OOZOU
تواصلوا معنا
العودة إلى المدونة

مقدمة إلى OTP في Elixir

في هذا الجزء من سلسلتنا التمهيدية عن Elixir، نحوّل خادم FizzBuzz الذي بنيناه في المرة السابقة إلى تطبيق OTP.

13 فبراير 2015
6 دقائق للقراءة
مقدمة إلى OTP في Elixir

ما هو OTP؟

بحسب التوثيق الرسمي، فإن OTP، أي منصة الاتصالات المفتوحة (Open Telecom Platform)، هي "بيئة تطوير متكاملة للبرمجة المتزامنة". وتضم مترجمًا ومفسّرًا للغة Erlang، وخادم قاعدة بيانات (Mnesia)، وأداة تحليل (Dyalizer)، إضافة إلى عدد كبير من المكتبات. وهذه المكتبات تحديدًا هي ما يقصده الناس عادة عندما يتحدثون عن OTP.

السلوكيات (Behaviours)

من أهم مبادئ التصميم في Erlang/OTP أنماط التطبيقات، أو ما يُسمّى "behaviours" بلغة OTP. تحدد هذه الأنماط تطبيقات عامة للمهام الشائعة، مثل وحدة الخادم العام (gen_server). يضيف مطوّر التطبيق الكود الخاص به في وحدة callback تصدّر مجموعة محددة من الدوال. وعلى خلاف Erlang، توفر Elixir تطبيقات افتراضية لهذه الدوال، فلا تحتاجون إلا إلى إعادة تعريف ما يلزمكم منها.

خادم FizzBuzz باستخدام GenServer

يكفي حديثًا عن النظرية، لنكتب بعض الكود!

نستخدم mix لإنشاء مشروع جديد. ولأننا نريد أن يكون اسم الوحدة بصيغة CamelCase، علينا تمرير الخيار module (كما في الأمر أدناه)، وإلا فسيكون الاسم الافتراضي Fizzbuzz.

$ mix new fizzbuzz --module FizzBuzz
lang-bash

ينشئ هذا الأمر قالب تطبيق كاملًا، لكننا في هذا المقال سنعمل على الملف lib/fizzbuzz.ex فقط.

لنبدأ بإخبار التطبيق بأننا نطبّق سلوك GenServer من OTP:

use GenServer
require Logger

استدعينا أيضًا وحدة Logger لأننا نستخدمها لأغراض التتبع وتصحيح الأخطاء. بعد ذلك نعرّف واجهة API الخاصة بالعملاء.

def start_link do
   GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end

def get(n) do
  GenServer.call(__MODULE__, {:print, n})
end

def print(n) do
  GenServer.cast(__MODULE__, {:print, n})
end

الدالة FizzBuzz.start_link/0 مجرد غلاف حول GenServer.start_link/3 التي تشغّل الخادم كعملية مرتبطة (linked process)، وغالبًا ما يكون ذلك ضمن شجرة إشراف (supervision tree). لاحظوا أننا نشغّل الخادم مع الخيار name ونعطيه اسم الوحدة (MODULE). بهذه الطريقة لا تحتاج الدوال الأخرى إلى الإشارة إلى الخادم عبر PID.

الدالتان FizzBuzz.get/1 وFizzBuzz.print/1 هما الواجهة الرئيسية للخادم. تستقبل كل منهما رقمًا وتعيد قيمة FizzBuzz المقابلة له أو تطبعها. يدعم GenServer نوعين من الطلبات: calls وcasts. النوع الأول متزامن ويُفترض أن يعيد شيئًا إلى العميل (دالة get لدينا)، أما الثاني فغير متزامن ولا يعيد بالضرورة أي شيء إلى العميل (دالة print لدينا).

من الشائع تغليف واجهة GenServer بواجهات API خاصة بنا للعملاء، لذا يستحق هذا النمط أن تتذكروه.

بعد ذلك ننتقل إلى تطبيق دوال callback اللازمة في GenServer.

def init(:ok) do
  Logger.debug "FizzBuzz server started"
  {:ok, %{}}
end

def handle_call({:print, n}, _from, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  {:reply, fb, state}
end

def handle_cast({:print, n}, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  IO.puts fb
  {:noreply, state}
end

تستدعي GenSever.start_link/3 الدالة init/1، وتعيد هذه الأخيرة tuple بالشكل {:ok, state}. وفي حالتنا هذه تكون الحالة (state) مجرد map بسيطة في Elixir (%{}).

الدالتان handle_call/2 وhandle_cast/2 هما قلب التطبيق. نستخدم pattern matching على الوسيط الأول لنحدد أننا نعالج الرسائل التي تأتي بالشكل {:print, n}. تسترجع الدالة الخاصة fetch_or_calculate/2 القيمة أو تحسبها، ثم نعيد الاستجابة المناسبة. في حالة call تكون الاستجابة بالشكل {:reply, response, state}، أما في حالة cast فتكون {:noreply, state}. هذه أعراف GenServer، ويسرد التوثيق جميع أنواع الردود الممكنة لكل نوع من الطلبات.

هكذا تبدو الدالة المساعدة الخاصة:

defp fetch_or_calculate(n, state) do
  if Dict.has_key?(state, n) do
    Logger.debug "Fetching #{n}"
    {:ok, fb} = Dict.fetch(state, n)
  else
    Logger.debug "Calculating #{n}"
    fb = fizzbuzz(n)
    state = Dict.put(state, n, fb)
  end
  {:ok, fb , state}
end

تتحقق fetch_or_calculate/2 مما إذا كانت قيمة FizzBuzz للرقم n قد حُسبت من قبل. إن كانت كذلك، نجلبها من القاموس. وإن لم تكن، نحسبها ونحدّث الحالة بإضافة القيمة الجديدة إليها (Dict.put(state, n, fb)). وفي النهاية نعيد tuple بالشكل {:ok, value, state} نطابقه في دوال المعالجة.

هذا كل شيء، أصبح خادم FizzBuzz جاهزًا للعمل! وإليكم الكود كاملًا:

defmodule FizzBuzz do
  use GenServer
  require Logger

  ## Client API

  def start_link do
     GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def get(n) do
    GenServer.call(__MODULE__, {:print, n})
  end

  def print(n) do
    GenServer.cast(__MODULE__, {:print, n})
  end

  ## Server Callbacks

  def init(:ok) do
    Logger.debug "FizzBuzz server started"
    {:ok, %{}}
  end

  def handle_call({:print, n}, _from, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    {:reply, fb, state}
  end

  def handle_cast({:print, n}, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    IO.puts fb
    {:noreply, state}
  end

  defp fetch_or_calculate(n, state) do
    if Dict.has_key?(state, n) do
      Logger.debug "Fetching #{n}"
      {:ok, fb} = Dict.fetch(state, n)
    else
      Logger.debug "Calculating #{n}"
      fb = fizzbuzz(n)
      state = Dict.put(state, n, fb)
    end
    {:ok, fb , state}
  end

  defp fizzbuzz(n) do
    case {rem(n, 3), rem(n, 5)} do
      {0, 0} -> :FizzBuzz
      {0, _} -> :Fizz
      {_, 0} -> :Buzz
      _      -> n
    end
  end
end

حان وقت استخدام الكود. من داخل مجلد المشروع، شغّلوا جلسة iex في سياق التطبيق بالأمر التالي:

iex -S mix

الآن يمكننا تشغيل الخادم وحساب بعض القيم:

FizzBuzz.start_link
4..6 |> Enum.map(&FizzBuzz.print/1)

هذا هو الناتج الذي يولّده الأمر. وبسبب الطبيعة غير المتزامنة للطلبات، يتداخل الناتج ورسائل السجل والقيمة التي تعيدها الدالة:

4

10:54:58.026 [debug] Calculating 4
Buzz
[:ok, :ok, :ok]

10:54:58.028 [debug] Calculating 5
Fizz

لاحظوا أن ناتج IO.puts ظهر في جلسة iex رغم أن الخادم يعمل في عملية منفصلة، لأن هذه الجلسة هي قائد المجموعة (group leader) الحالي.

وإذا حاولنا الآن جلب قيمة سبق حسابها (FizzBuzz.get(5))، فسنرى أنها تُسترجع فعلًا من الـ cache:

10:58:56.774 [debug] Fetching 5
:Buzz

الخلاصة

استعرضنا في هذا المقال مدى سهولة بناء تطبيقات الخوادم بالاستفادة من OTP. أصبح خادم FizzBuzz الآن جاهزًا للنشر كجزء من تطبيق Elixir أكبر، مع ميزات مثل الإشراف (supervision) والاستبدال الحي للكود (hot code swapping).

مصادر

  • توثيق OTP الرسمي
  • دليل OTP في Elixir
  • The Little Elixir and OTP Guidebook

Related Articles

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

تصميم البرمجيات عملية ممتعة لكنها معقدة، تتطلب الإبداع والتفكير المنطقي ومهارات حل المشكلات.

اقرأوا المزيد
مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

يتطور المشهد الرقمي بسرعة، وعلى الشركات أن تتكيّف لتواكب الطلب المتزايد على السرعة والكفاءة والمعالجة في الوقت الفعلي.

اقرأوا المزيد
مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

يتطور قطاع التجزئة بوتيرة غير مسبوقة بفضل الذكاء الاصطناعي (AI) والواقع المعزّز (AR) وتقنيات التحوّل الرقمي. فلم تعد المتاجر التقليدية مجرد أماكن لبيع المنتجات، بل أصبحت متاجر ذكية وتفاعلية.

اقرأوا المزيد

Have a Project in Mind?

Let's discuss how we can help bring your ideas to life with our expertise in web and mobile development.

ابدأوا المحادثةhello@oozou.com
OOZOU Logo

بانكوك · سنغافورة · هونغ كونغ

X
Facebook
Instagram
LinkedIn
OOZOU Co., Ltd.
548 One City Centre
Ploenchit Road
Bangkok 10330
Thailand
+66 2544 0000
hello@oozou.com

الخدمات

  • تطوير الويب
  • وكلاء الذكاء الاصطناعي والذكاء الاصطناعي التوليدي
  • تطوير تطبيقات الجوال
  • تحليلات البيانات وهندستها
  • تصميم تجربة المستخدم وواجهته (UX/UI) وتصميم المنتجات
  • التحوّل الرقمي

الشركة

  • من نحن
  • الوظائف
  • المدونة
  • دراسات الحالة
  • عروض حية
  • تعلّمت اليوم
  • تواصل معنا

المزيد

  • القطاعات
  • شبكة الشركاء
© 2026 OOZOU. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد