أبسط JSON RESTful API على إكسير

كيفية تطبيق JSON API endpoint على Elixir دون أي أطر؟


من المترجم:
تقدم المقالة مثالًا على تطبيق ويب بسيط للغاية يمكن اعتباره Hello، World! في إنشاء أبسط API على إكسير.
يتم تعديل نموذج التعليمة البرمجية قليلاً لمطابقة الإصدارات الحالية من المكتبات.
يمكن مشاهدة رمز عينة كامل مع التغييرات على جيثب .



تحديات لغوية جديدة


يأتي العديد من المطورين إلى Elixir من عالم Ruby. هذه بيئة ناضجة للغاية من حيث عدد المكتبات والأطر المتاحة. ومثل هذا النضج في بعض الأحيان لا يكفي بالنسبة لي في إكسير. عندما أحتاج إلى خدمة تابعة لجهة خارجية ، قد تكون نتيجة البحث المناسب كما يلي:


  • هناك مكتبة رسمية مدعومة جيدًا (نادرة جدًا) ؛
  • هناك مكتبة رسمية ، ولكنها قديمة أو عربات التي تجرها الدواب (يحدث أحيانًا) ؛
  • توجد مكتبة مدعومة جيدًا وضعها شخص من المجتمع (أحيانًا من وقت لآخر) ؛
  • توجد مكتبة قام بتطويرها شخص من المجتمع ، ولكنها لم تعد مدعومة (حالة شائعة جدًا) ؛
  • هناك العديد من المكتبات ، كل منها مكتوب من قبل شخص لاحتياجاته الخاصة ، ويفتقر إلى الميزات الضرورية (الخيار الأكثر شعبية) ؛
  • هناك مكتبتي الخاصة ، والتي تجمع بين كل ما سبق ... (وجدت في كثير من الأحيان).

بسيطة JSON API على إكسير



قد تتفاجأ ، لكن روبي ليس دائمًا على القضبان ( روبي أون ريلز ، تذكر؟ - ملاحظة المترجم ). التواصل مع الويب ليس مطلوبًا دائمًا للحضور. رغم أننا في هذه الحالة بالذات ، دعنا نتحدث عن الويب.


عندما يتعلق الأمر بتنفيذ نقطة نهاية RESTful واحدة ، عادة ما يكون هناك العديد من الخيارات:



هذه أمثلة على الأدوات التي استخدمتها شخصيًا. رضى زملائي المستخدمين سيناترا. تمكنوا من تجربة Hanami. يمكنني اختيار أي خيار يناسبني ، حتى اعتمادًا على مزاجي الحالي.


لكن عندما تحولت إلى Elixir ، اتضح أن الخيار كان محدودًا. على الرغم من وجود العديد من "الأطر" البديلة (التي لن أذكرها هنا لأسباب واضحة) ، فإنه يكاد يكون من المستحيل استخدامها!


قضيت طوال اليوم في فرز كل مكتبة سبق ذكرها على الإنترنت. بصفتي بوت Slack ، حاولت نشر خادم HTTP2 بسيط على Heroku ، لكنني استسلمت في نهاية اليوم. حرفيًا ، لم يكن أي من الخيارات التي وجدتها قادراً على تنفيذ المتطلبات الأساسية.


ليس دائما حلا - فينيكس


فينيكس هو إطار الويب المفضل لدي ، وهو أنه في بعض الأحيان يكون ضروريًا. لم أكن أرغب في استخدامه ، وسحب الإطار بأكمله إلى المشروع فقط من أجل نقطة نهاية واحدة ؛ ولا يهم أن الأمر بسيط للغاية.


لم أستطع استخدام المكتبات الجاهزة أيضًا ، كما قلت سابقًا ، كانت جميع المكتبات التي عثرت عليها إما غير مناسبة لاحتياجاتي (يلزم التوجيه الأساسي ودعم JSON) ، أو لم تكن ملائمة بما يكفي للنشر السهل والسريع في Heroku. خذ خطوة إلى الوراء ، فكرت.



ولكن في الواقع ، تم بناء فينيكس نفسها على أساس شيء ، أليس كذلك؟


التوصيل وكاوبوي تأتي لإنقاذ


إذا كنت بحاجة إلى إنشاء خادم أضيق الحدود حقًا على Ruby ، ​​فيمكنك ببساطة استخدام rack - واجهة معيارية لخوادم الويب Ruby.


لحسن الحظ ، هناك شيء مماثل متاح في Elixir. في هذه الحالة ، سوف نستخدم العناصر التالية:


  • cowboy هو خادم HTTP صغير وسريع لـ Erlang / OTP يقوم بتنفيذ مكدس HTTP الكامل وتوجيهه ، وهو الأمثل لتقليل استخدام الكمون والذاكرة ؛
  • plug - مجموعة من المحولات لخوادم الويب المختلفة التي تعمل في Erlang VM ؛ يوفر كل محول واجهة مباشرة لخادم الويب الموجود خلفه ؛
  • السم هو مكتبة لمعالجة JSON على إكسير.

تطبيق


أريد تنفيذ مكونات مثل Endpoint (نقطة النهاية) ، Router (جهاز التوجيه) و JSON Parser (معالج JSON). ثم أود أن نشر الناتج على Heroku وتكون قادرة على معالجة الطلبات الواردة. دعونا نرى كيف يمكن تحقيق ذلك.


تطبيق


تأكد من أن مشروع Elixir يحتوي على مشرف. للقيام بذلك ، قم بإنشاء مشروع مثل هذا:


 mix new minimal_server --sup 

تأكد من أن mix.exs يحتوي على:


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

وقم بإنشاء الملف 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 

المكتبات


يجب تحديد المكتبات التالية في mix.exs :


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

ثم قم بتنزيل وتجميع التبعيات:


 mix do deps.get, deps.compile, compile 

نقطة النهاية


كل شيء جاهز الآن لإنشاء نقطة دخول إلى الخادم. لنقم بإنشاء ملف lib/minimal_server/endpoint.ex بالمحتويات التالية:


 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 

تحتوي وحدة Plug على Plug.Router لإعادة توجيه الطلبات الواردة وفقًا للمسار المستخدم وطريقة HTTP. عند استلام الطلب ، سيتصل الموجّه بالوحدة :match ، ممثلة بوظيفة match/2 ، المسؤولة عن إيجاد المسار المقابل ، ثم تعيد توجيهها إلى الوحدة النمطية :dispatch ، والتي ستنفذ الرمز المقابل.


نظرًا لأننا نريد أن تكون واجهة برمجة التطبيقات الخاصة بنا متوافقة مع JSON ، فإننا نحتاج إلى تطبيق Plug.Parsers . نظرًا لأنه يعالج application/json باستخدام المحدد :json_decoder ، application/json لتحليل نص الطلب.


نتيجة لذلك ، أنشأنا مسارًا مؤقتًا "أي طلب" يطابق جميع الطلبات ويستجيب برمز HTTP غير موجود (404).


جهاز التوجيه


سيكون تنفيذ جهاز التوجيه الخطوة الأخيرة في إنشاء تطبيقنا. هذا هو العنصر الأخير في خط الأنابيب بالكامل الذي أنشأناه: بدء تلقي طلب من مستعرض ويب وتنتهي بتكوين استجابة.


سيقوم الموجه بمعالجة الطلب الوارد من العميل وإعادة إرسال بعض الرسائل بالتنسيق المطلوب ( أضف الكود أعلاه إلى ملف lib/minimal_server/router.ex - ملاحظة المترجم ):


 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 

في وحدة Router أعلاه ، لن تتم معالجة الطلب إلا إذا تم إرساله بواسطة طريقة GET وإرساله على طول المسار / . ستستجيب وحدة التوجيه (router) برأس Content-Type يحتوي على application/json :


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

وضع كل ذلك معا


الآن هو الوقت المناسب لتغيير وحدة Endpoint لإعادة توجيه الطلبات إلى جهاز التوجيه وتعديل Application Endpoint وحدة Endpoint نفسها.


الأول يمكن القيام به عن طريق إضافة إلى MinimalServer.Endpoint [ قبل match _ do ... end القاعدة - تقريبا. مترجم ] سلسلة


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

هذا يضمن أن جميع الطلبات إلى /bot سيتم توجيهها ومعالجتها بواسطة وحدة Router .


يمكن تنفيذ الثانية عن طريق إضافة وظائف child_spec/1 و start_link/1 ملف 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 

يمكنك الآن تعديل application.ex عن طريق إضافة MinimalServer.Endpoint إلى القائمة التي أرجعتها الدالة children/0 .


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

لبدء الخادم ، فقط قم بما يلي:


 mix run --no-halt 

أخيرًا يمكنك زيارة العنوان http: // localhost: 4000 / bot ورؤية رسالتنا :)


نشر



التكوين


في معظم الأحيان ، في بيئة محلية وللتشغيل ، يتم تكوين الخادم بشكل مختلف. لذلك ، نحتاج إلى إدخال إعدادات منفصلة لكل من هذه الأوضاع. بادئ ذي config.exs ، قم config.exs لدينا عن طريق إضافة:


 config :minimal_server, MinimalServer.Endpoint, port: 4000 

في هذه الحالة ، عندما يبدأ التطبيق في test ووضع prod و dev ، سيتلقى المنفذ 4000 إذا لم يتم تغيير هذه الإعدادات.


من المترجم

في هذه المرحلة ، نسي مؤلف النص الأصلي ذكر كيفية تعديل config.exs بحيث يمكنك استخدام خيارات مختلفة لأوضاع مختلفة. للقيام بذلك ، أضف import_config "#{Mix.env()}.exs" ؛ في السطر الأخير في config/config.exs ؛ والنتيجة هي شيء مثل:


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

بعد ذلك ، قم بإنشاء ملفات prod.exs ، test.exs ، dev.exs في دليل config بوضعها على كل سطر:


 use Mix.Config 

في الإنتاج ، لا نريد عادة تعيين رقم المنفذ بشدة ، ولكننا نعتمد على متغير بيئة النظام ، على سبيل المثال:


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

أضف النص أعلاه إلى نهاية config/prod.exs - تقريبا. ترجمة


بعد ذلك ، سيتم استخدام قيمة ثابتة محليًا ، وفي التشغيل التشغيلي ، تكوين لمتغيرات البيئة.


لننفذ هذا المخطط في endpoint.ex ، ( استبدال وظيفة start_link / 1 - تعليق المترجم ):


 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 أبسط نشر بنقرة واحدة دون أي إعداد معقد. لنشر مشروعنا ، تحتاج إلى إعداد بعض الملفات البسيطة وإنشاء تطبيق عن بُعد .



بعد تثبيت Heroku CLI ، يمكنك إنشاء تطبيق جديد كما يلي:


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

أضف الآن Elixir Build Kit إلى تطبيقك:


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

في وقت هذه الترجمة ، كانت الإصدارات الحالية من Elixir و Erlang (زائد أو ناقص):


 erlang_version=21.1 elixir_version=1.8.1 

لتكوين مجموعة أدوات البناء نفسها ، أضف الأسطر أعلاه إلى ملف elixir_buildpack.config .


الخطوة الأخيرة هي إنشاء ملف Procfile ، ومرة ​​أخرى بسيطة للغاية:


 web: mix run --no-halt 

ملاحظة المترجم: لتجنب حدوث خطأ أثناء الإنشاء على Heroku ، تحتاج إلى تعيين قيمة متغيرات البيئة المستخدمة في التطبيق:


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

بمجرد ارتكاب ملفات جديدة [ باستخدام بوابة - تقريبا. المترجم ] ، يمكنك تحميلها على Heroku:


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

وهذا كل شيء! التطبيق متاح في https://minimal-server-habr.herokuapp.com .


ملخص


في هذه المرحلة ، فهمت بالفعل كيفية تنفيذ أبسط خادم JSON RESTful API وخادم HTTP على Elixir دون استخدام أي أطر عمل ، وذلك باستخدام مكتبات 3 فقط ( 4 - مترجم تقريبًا ).


عندما تحتاج إلى توفير الوصول إلى نقاط النهاية البسيطة ، فلن تحتاج مطلقًا إلى استخدام Phoenix في كل مرة ، بغض النظر عن مدى روعتها ، وكذلك أي إطار آخر.


فضولي لماذا لا توجد أطر موثوقة ومختبرة ومدعومة في مكان ما بين plug + cowboy و Phoenix؟ ربما ليست هناك حاجة حقيقية لتنفيذ أشياء بسيطة؟ ربما تستخدم كل شركة مكتبتها الخاصة؟ أو ربما يستخدم الجميع إما فينيكس أو النهج المقدم؟



المستودع ، كما هو الحال دائمًا ، متاح على GitHub.

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


All Articles