API tenang JSON sederhana di Elixir

Bagaimana cara mengimplementasikan JSON endpoint API di Elixir tanpa kerangka kerja?


Dari penerjemah:
Artikel ini memberikan contoh aplikasi web yang sangat sederhana yang dapat dianggap sebagai Hello, World! dalam membuat API paling sederhana di Elixir.
Kode sampel sedikit dimodifikasi untuk mencocokkan versi perpustakaan saat ini.
Kode sampel lengkap dengan perubahan dapat dilihat di GitHub .



Tantangan Bahasa Baru


Banyak pengembang datang ke Elixir dari dunia Ruby. Ini adalah lingkungan yang sangat matang dalam hal jumlah perpustakaan dan kerangka kerja yang tersedia. Dan kedewasaan seperti itu terkadang tidak cukup bagi saya di Elixir. Ketika saya membutuhkan layanan pihak ketiga, hasil pencarian yang sesuai mungkin sebagai berikut:


  • ada perpustakaan resmi yang didukung dengan baik (sangat jarang);
  • ada perpustakaan yang resmi, tetapi sudah ketinggalan zaman atau bermasalah (kadang-kadang terjadi);
  • Ada perpustakaan yang didukung dengan baik yang dikembangkan oleh seseorang dari komunitas (kadang-kadang dari waktu ke waktu);
  • ada perpustakaan yang dikembangkan oleh seseorang dari komunitas, tetapi tidak lagi didukung (kasus yang sangat umum);
  • ada beberapa perpustakaan, yang masing-masing ditulis oleh seseorang untuk kebutuhan mereka sendiri, dan tidak memiliki fitur yang diperlukan (opsi paling populer);
  • ada perpustakaan saya sendiri , menggabungkan semua yang terbaik di atas ... (terlalu sering ditemukan).

API JSON sederhana di Elixir



Anda mungkin terkejut, tetapi Ruby tidak selalu di rel ( Ruby on Rails, ingat? - Catatan Penerjemah ). Komunikasi dengan web juga tidak selalu diperlukan untuk hadir. Meskipun dalam kasus khusus ini, mari kita bicara tentang web.


Ketika mengimplementasikan titik akhir RESTful tunggal, biasanya ada banyak pilihan:



Ini adalah contoh alat yang saya gunakan secara pribadi. Rekan-rekan saya puas dengan pengguna Sinatra. Mereka berhasil mencoba Hanami. Saya dapat memilih opsi yang cocok untuk saya, bahkan tergantung pada suasana hati saya saat ini.


Tetapi ketika saya beralih ke Elixir, ternyata pilihannya terbatas. Meskipun ada beberapa "kerangka" alternatif (yang namanya karena alasan yang jelas tidak akan saya sebutkan di sini), hampir tidak mungkin untuk menggunakannya!


Saya menghabiskan sepanjang hari memilah-milah setiap perpustakaan yang pernah disebutkan di Internet. Bertindak sebagai bot Slack, saya mencoba menggunakan server HTTP2 sederhana untuk Heroku , tetapi menyerah pada akhir hari. Secara harfiah, tidak ada opsi yang saya temukan dapat menerapkan persyaratan dasar.


Tidak selalu solusi - Phoenix


Phoenix adalah kerangka web favorit saya, hanya saja kadang-kadang berlebihan. Saya tidak ingin menggunakannya, menarik seluruh kerangka kerja ke dalam proyek semata-mata demi satu titik akhir; dan tidak masalah bahwa itu sangat sederhana.


Saya juga tidak bisa menggunakan perpustakaan yang sudah jadi, karena, seperti yang sudah saya katakan, semua perpustakaan yang saya temukan tidak cocok untuk kebutuhan saya (diperlukan perutean dasar dan dukungan JSON), atau tidak cukup nyaman untuk penyebaran Heroku yang mudah dan cepat. Ambil langkah mundur, pikirku.



Tapi sebenarnya, Phoenix sendiri dibangun berdasarkan sesuatu , bukan?


Pasang & Koboi datang untuk menyelamatkan


Jika Anda perlu membuat server yang benar-benar minimalis di Ruby, Anda cukup menggunakan rack - antarmuka modular untuk server web Ruby.


Untungnya, sesuatu yang serupa tersedia di Elixir. Dalam hal ini, kami akan menggunakan elemen-elemen berikut:


  • cowboy adalah server HTTP kecil dan cepat untuk Erlang / OTP yang mengimplementasikan stack HTTP penuh dan routing, dioptimalkan untuk meminimalkan latensi dan penggunaan memori;
  • plug - satu set adapter untuk berbagai server web yang berjalan di Erlang VM; setiap adaptor menyediakan antarmuka langsung ke server web yang terletak di belakangnya;
  • poison adalah perpustakaan untuk memproses JSON di Elixir.

Implementasi


Saya ingin mengimplementasikan komponen seperti Endpoint (endpoint), Router (router) dan JSON Parser (JSON handler). Kemudian saya ingin menggunakan hasilnya pada Heroku dan dapat memproses permintaan yang masuk. Mari kita lihat bagaimana ini bisa dicapai.


Aplikasi


Pastikan proyek Elixir Anda berisi seorang supervisor. Untuk melakukan ini, buat proyek seperti ini:


 mix new minimal_server --sup 

Pastikan mix.exs mengandung:


 def application do [ extra_applications: [:logger], mod: {MinimalServer.Application, []} ] end 

dan buat file lib/minimal_server/application.ex :


 defmodule MinimalServer.Application do use Application def start(_type, _args), do: Supervisor.start_link(children(), opts()) defp children do [] end defp opts do [ strategy: :one_for_one, name: MinimalServer.Supervisor ] end end 

Perpustakaan


Pustaka berikut harus ditentukan dalam mix.exs :


 defp deps do [ {:poison, "~> 4.0"}, {:plug, "~> 1.7"}, {:cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.0"} ] end 

Kemudian unduh dan kompilasi dependensi:


 mix do deps.get, deps.compile, compile 

Titik akhir


Sekarang semuanya siap untuk membuat titik masuk ke server. Mari kita membuat file lib/minimal_server/endpoint.ex dengan konten berikut:


 defmodule MinimalServer.Endpoint do use Plug.Router plug(:match) plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Poison ) plug(:dispatch) match _ do send_resp(conn, 404, "Requested page not found!") end end 

Modul Plug berisi Plug.Router untuk mengarahkan ulang permintaan yang masuk tergantung pada jalur yang digunakan dan metode HTTP. Setelah menerima permintaan, router akan memanggil module :match , diwakili oleh fungsi match/2 , yang bertanggung jawab untuk menemukan rute yang sesuai, dan kemudian mengarahkannya ke module :dispatch , yang akan menjalankan kode yang sesuai.


Karena kami ingin API kami sesuai dengan JSON, kami perlu mengimplementasikan Plug.Parsers . Karena memproses permintaan application/json dengan diberikan :json_decoder , kita akan menggunakannya untuk menganalisis tubuh permintaan.


Akibatnya, kami membuat rute sementara "permintaan apa pun" yang cocok dengan semua permintaan dan merespons dengan kode HTTP not found (404).


Router


Menerapkan router akan menjadi langkah terakhir dalam membuat aplikasi kita. Ini adalah elemen terakhir dari keseluruhan pipa yang kami buat: dimulai dengan menerima permintaan dari browser web dan diakhiri dengan pembentukan respons.


Router akan memproses permintaan yang masuk dari klien dan mengirimkan kembali beberapa pesan dalam format yang diinginkan ( tambahkan kode di atas ke file lib/minimal_server/router.ex - catatan penerjemah ):


 defmodule MinimalServer.Router do use Plug.Router plug(:match) plug(:dispatch) get "/" do conn |> put_resp_content_type("application/json") |> send_resp(200, Poison.encode!(message())) end defp message do %{ response_type: "in_channel", text: "Hello from BOT :)" } end end 

Dalam modul Router atas, permintaan hanya akan diproses jika dikirim dengan metode GET dan dikirim di sepanjang rute / . Modul Router akan merespons dengan header Content-Type berisi application/json dan tubuh:


 { "response_type": "in_channel", "text": "Hello from BOT :)" } 

Menyatukan semuanya


Sekarang adalah waktunya untuk mengubah modul Endpoint untuk meneruskan permintaan ke router dan memodifikasi Application untuk meluncurkan modul Endpoint itu sendiri.


Yang pertama dapat dilakukan dengan menambahkan ke MinimalServer.Endpoint [ sebelum match _ do ... end rule - approx. translator ] string


 forward("/bot", to: MinimalServer.Router) 

Ini memastikan bahwa semua permintaan ke /bot akan dialihkan ke dan diproses oleh modul Router .


Yang kedua dapat diimplementasikan dengan menambahkan fungsi child_spec/1 dan start_link/1 file endpoint.ex :


 defmodule MinimalServer.Endpoint do # ... def child_spec(opts) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [opts]} } end def start_link(_opts), do: Plug.Cowboy.http(__MODULE__, []) end 

Sekarang Anda dapat memodifikasi application.ex dengan menambahkan MinimalServer.Endpoint ke daftar yang dikembalikan oleh fungsi children/0 .


 defmodule MinimalServer.Application do # ... defp children do [ MinimalServer.Endpoint ] end end 

Untuk memulai server, lakukan saja:


 mix run --no-halt 

Akhirnya Anda bisa mengunjungi alamat http: // localhost: 4000 / bot dan lihat pesan kami :)


Penempatan



Konfigurasi


Paling sering, di lingkungan lokal dan untuk operasi, server dikonfigurasi secara berbeda. Karena itu, kita perlu memasukkan pengaturan terpisah untuk masing-masing mode ini. Pertama-tama, config.exs kami dengan menambahkan:


 config :minimal_server, MinimalServer.Endpoint, port: 4000 

Dalam hal ini, ketika aplikasi mulai dalam mode test , prod dan dev , ia akan menerima port 4000 jika pengaturan ini tidak diubah.


Dari penerjemah

Pada titik ini, penulis teks asli lupa menyebutkan cara memodifikasi config.exs sehingga Anda dapat menggunakan opsi yang berbeda untuk mode yang berbeda. Untuk melakukan ini, tambahkan import_config "#{Mix.env()}.exs" ; di baris terakhir di config/config.exs ; hasilnya adalah sesuatu seperti:


 use Mix.Config config :minimal_server, MinimalServer.Endpoint, port: 4000 import_config "#{Mix.env()}.exs" 

Setelah itu, buat file prod.exs , test.exs , dev.exs di direktori config dengan menempatkannya di setiap baris:


 use Mix.Config 

Dalam produksi, kami biasanya tidak ingin mengatur nomor port keras, tetapi bergantung pada beberapa variabel lingkungan sistem, misalnya:


 config :minimal_server, MinimalServer.Endpoint, port: "PORT" |> System.get_env() |> String.to_integer() 

Tambahkan teks di atas ke akhir config/prod.exs - kira-kira. penerjemah


Setelah itu, nilai tetap akan digunakan secara lokal, dan dalam operasi operasional, konfigurasi variabel lingkungan.


Mari kita implementasikan skema ini di endpoint.ex , ( mengganti fungsi start_link / 1 - komentar penerjemah ):


 defmodule MinimalServer.Endpoint do # ... require Logger def start_link(_opts) do with {:ok, [port: port] = config} <- Application.fetch_env(:minimal_server, __MODULE__) do Logger.info("Starting server at http://localhost:#{port}/") Plug.Adapters.Cowboy2.http(__MODULE__, [], config) end end end 

Heroku


Heroku menawarkan penyebaran satu klik paling sederhana tanpa pengaturan rumit. Untuk menggunakan proyek kami, Anda perlu menyiapkan beberapa file sederhana dan membuat aplikasi jarak jauh .



Setelah menginstal Heroku CLI, Anda dapat membuat aplikasi baru sebagai berikut:


 $ heroku create minimal-server-habr Creating ⬢ minimal-server-habr... done https://minimal-server-habr.herokuapp.com/ | https://git.heroku.com/minimal-server-habr.git 

Sekarang tambahkan Elixir Build Kit ke aplikasi Anda:


 heroku buildpacks:set \ https://github.com/HashNuke/heroku-buildpack-elixir.git 

Pada saat terjemahan ini, versi Elixir dan Erlang saat ini adalah (plus atau minus):


 erlang_version=21.1 elixir_version=1.8.1 

Untuk mengkonfigurasi kit bangun itu sendiri, tambahkan baris di atas ke file elixir_buildpack.config .


Langkah terakhir adalah membuat Procfile, dan, sekali lagi, ini sangat sederhana:


 web: mix run --no-halt 

Catatan Penerjemah: untuk menghindari kesalahan selama pembuatan di Heroku, Anda harus menetapkan nilai variabel lingkungan yang digunakan dalam aplikasi:


 $ heroku config:set PORT=4000 Setting PORT and restarting ⬢ minimal-server-habr... done, v5 PORT: 4000 

Segera setelah Anda mengkomit file baru [ menggunakan git - kira - kira. penerjemah ], Anda dapat mengunggahnya ke Heroku:


 $ git push heroku master Initializing repository, done. updating 'refs/heads/master' ... 

Dan itu saja! Aplikasi ini tersedia di https://minimal-server-habr.herokuapp.com .


Ringkasan


Pada titik ini, Anda sudah mengerti bagaimana menerapkan API JSON RESTful dan server HTTP paling sederhana ke Elixir tanpa menggunakan kerangka kerja apa pun, hanya menggunakan 3 ( sekitar 4 - Penerjemah ) perpustakaan.


Ketika Anda perlu memberikan akses ke titik akhir sederhana, Anda benar-benar tidak perlu menggunakan Phoenix setiap kali, tidak peduli seberapa keren itu, serta kerangka kerja lainnya.


Penasaran mengapa tidak ada kerangka kerja yang andal, teruji dan didukung di suatu tempat antara plug + cowboy dan Phoenix? Mungkin tidak ada kebutuhan nyata untuk mengimplementasikan hal-hal sederhana? Mungkin masing-masing perusahaan menggunakan perpustakaannya sendiri? Atau mungkin semua orang menggunakan Phoenix atau pendekatan yang disajikan?



Repositori , seperti biasa, tersedia di GitHub saya.

Source: https://habr.com/ru/post/id444554/


All Articles