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 penerjemahPada 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.