---
title: "فهم تطبيقات OTP في Elixir، الجزء 1: التوزيع"
description: "أحب Elixir. بحكم قدومي من Ruby، أجد الصياغة مألوفة وأستمتع بما تنفّذه Elixir بطريقة مختلفة. سلسلة عن مبادئ Elixir وErlang، يتناول هذا الجزء الأنظمة الموزعة والعمليات."
canonical: https://oozou.com/ar/blog/understanding-elixir-otp-applications-part-1-distribution-79
locale: ar
date: 2020-11-25
author: "OOZOU Team"
---

# فهم تطبيقات OTP في Elixir، الجزء 1: التوزيع

> أحب Elixir. بحكم قدومي من Ruby، أجد نفسي في أرض مألوفة من حيث الصياغة، وأستمتع في الوقت نفسه ببعض الأمور التي تنفّذها Elixir بطريقة مختلفة. أكتب سلسلة عن مبادئ Elixir وErlang وكيف تعمل من الداخل. يتناول هذا المقال الأنظمة الموزعة والعمليات.

Published: 2020-11-25 · Author: OOZOU Team (Engineering) · 5 min read

بُنيت Elixir فوق Erlang. وErlang لغة طُوّرت في شركة الاتصالات Ericsson، التي عُرفت لاحقًا باسم Sony Ericsson. حين صمّم المهندسون الأوائل [Open Telecom Protocol](https://en.wikipedia.org/wiki/Open_Telecom_Platform) الخاص بـ Erlang، كانت لديهم تصورات عن الخصائص التي يحتاجها النظام ليتعامل مع بنية الاتصالات لديهم بموثوقية.
صُمّمت OTP لتكون موزّعة، ومتحمّلة للأعطال، وتعمل في الوقت الفعلي، وعالية التوفر، وقابلة للاستبدال أثناء التشغيل (hot-swappable).
هذا كثير على الاستيعاب، فماذا يعني كل ذلك؟ سأشرح في هذا المقال كيف يعمل الجانب الموزّع من هذه المنظومة.
مفاهيم Erlang وElixir متبادلة إلى حد كبير. فكثير من الوحدات (modules) في Elixir هي تجريدات فوق تجريدات Erlang، لذا يمكنكم اعتبارها المفهوم نفسه. سأناقش في هذا المقال كيف تعمل [Processes](https://hexdocs.pm/elixir/Process.html) و[GenServer](https://hexdocs.pm/elixir/GenServer.html) في Elixir، وهما في صميم أنظمتها الموزعة.

# التواصل مع العمليات الموزعة عبر صناديق البريد

صُمّمت OTP لتعمل على [الشبكات الموزعة](https://en.wikipedia.org/wiki/Distributed_networking#:~:text=Distributed%20networking%2C%20used%20in%20distributed,are%20dependent%20upon%20each%20other.). وهي تحقق ذلك عبر نظام صناديق البريد (mailbox). يمكن لأي عملية (Process) في Elixir أن تُنشئ عمليات فرعية، وهذه العمليات لا تتشارك أي ذاكرة، لذا فهي معزولة تمامًا. وللتواصل فيما بينها، ترسل الرسائل إلى صندوق بريد العملية الذي يُعرَّف بمعرّف العملية (PID). يمكن لكل عملية أن ترسل رسائل إلى عمليات أخرى، وأن تضبط مستمعًا على صندوق بريدها لمعالجة الرسائل الواردة.
لنبدأ بإنشاء عملية بسيطة تعيد رسالة إلى صندوق بريد العملية الأم:

```elixir
parent = self()
spawn fn -> send(parent, {:ok, self()}) end
Process.info(parent, :messages)
=> {:messages, [ok: #PID<0.000.0>]}
```

أولًا نحصل على PID العملية الحالية لنستخدمه داخل الدالة المجهولة التي نمررها إلى spawn.
وداخل الدالة المُنشأة، نستدعي [send/3](https://hexdocs.pm/elixir/Process.html#send/3). الوسيط الأول هو العملية التي نرسل إليها الرسالة، والثاني هو الرسالة نفسها. في هذه الحالة نرسل tuple يحتوي على :ok وPID العملية المُنشأة. يمكن أن تكون الرسالة بأي صيغة تريدونها، ومن الشائع في Elixir استخدام tuples حتى نطابق العنصر الأول ونعرف كيف نعالج العنصر الثاني.
يمكننا فحص الرسائل باستخدام [Process.info/2](https://hexdocs.pm/elixir/Process.html#info/2)، وسنرى أن رسالتنا قد عادت بالفعل.
والآن لنعالج هذه الرسائل:

```elixir
parent = self()
spawn fn -> send(parent, {:ok, self()}) end
receive do
  {:ok, pid} -> "Got ok from #{inspect pid}"
  _ -> "Unknown message type"
end
```

تتيح لنا الدالة [receive/1](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1) إنشاء مستمع ينتظر وصول رسالة إلى صندوق بريد العملية الحالية. وداخل كتلة receive يمكننا مطابقة نوع الرسالة التي استقبلناها (pattern matching). إن بدت لكم صياغة receive مربكة، فذلك لأنها macro من نوع [SpecialForm](https://hexdocs.pm/elixir/Kernel.SpecialForms.html).
حسنًا، لننشئ الآن حلقة تكرار ونطلق عملية مرتبطة (linked)، حتى نضمن ألا تنتهي عمليتنا ببساطة. بهذا الإعداد البسيط يمكننا تخزين الحالة (state)، أو في هذا المثال البسيط مجرد إعادة رسالة تحاكي روبوت محادثة بدائيًا.

```elixir
defmodule Bot do
  def start_link do
    Task.start_link(fn -> loop() end)
  end

  defp loop() do
    receive do
      {:help, caller} ->
        send caller, "You can ask me what today's date is by passing :date_today"
        loop()
      {:date_today, caller} ->
        send caller, "Today's date is #{Date.utc_today()}"
        loop()
    end
  end
end

{:ok, pid} = Bot.start_link
send pid, {:help, self()}
flush()
=> "You can ask me what today's date is by passing :date_today"
=> :ok
send pid, {:date_today, self()}
=> "Today's date is 2020-11-20"
=> :ok
```

لاحظوا كيف تعيد المهمة المُنشأة تشغيل الحلقة في كل مرة تُعالَج فيها رسالة داخل receive. هذا هو الأساس الذي يقوم عليه GenServer.

# فهم واجهة GenServer

حين بنى المطورون مكتبات OTP، أدركوا أن هذا النمط سيكون شائعًا، فأنشأوا تجريدًا يتولى عنا الحلقة ومعالجة الرسائل.
يمكن للوحدة التي تطبّق GenServer أن تطبّق عددًا من دوال الاستدعاء الراجع (callbacks). سنستخدم [init/1](https://hexdocs.pm/elixir/GenServer.html#c:init/1) و[handle_cast/2](https://hexdocs.pm/elixir/GenServer.html#c:handle_cast/2) و[handle_call/3](https://hexdocs.pm/elixir/GenServer.html#c:handle_call/3). بعد ذلك يمكننا استخدام GenServer لبدء عملية تعمل في حلقة، وتنتظر [cast/2](https://hexdocs.pm/elixir/GenServer.html#cast/2) أو [call/3](https://hexdocs.pm/elixir/GenServer.html#call/3). وحين تستقبل العملية cast/2 أو call/3، تستدعي الدالتين handle_cast/2 وhandle_call/3 على التوالي.
لننشئ مخزنًا للمفاتيح والقيم (key-value store)، نسخة مبسطة من Redis أو Memcached. ولتسهيل الاستخدام وتحسين قابلية القراءة، سنضيف الدوال start/1 وset/3 وget/2 وdelete/2 لتغليف استدعاءات GenServer.cast/2 وGenServer.call/3

```elixir
defmodule KeyValueStore do
  use GenServer

  def start(initial_state \\ %{}) do
    GenServer.start(__MODULE__, initial_state)
  end

  def set(server, key, value) do
    GenServer.cast(server, {:set, key, value})
  end

  def get(server, key) do
    GenServer.call(server, {:get, key})
  end

  def delete(server, key) do
    GenServer.cast(server, {:delete, key})
  end

  def init(state) do
    {:ok, state}
  end

  def handle_cast({:set, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  def handle_cast({:delete, key}, state) do
    {:noreply, Map.delete(state, key)}
  end

  def handle_call({:get, key}, _from, state) do
    value = case Map.fetch(state, key) do
      {:ok, v} -> v
      :error -> nil
    end
    {:reply, value, state}
  end
end
```

الآن يمكننا تشغيل KeyValueStore، وتمرير أزواج المفاتيح والقيم إليه، واسترجاعها لاحقًا.

```elixir
{:ok, pid} = KeyValueStore.start
Process.register(pid, :primary_store)
:primary_store
:primary_key_value_store |> KeyValueStore.set(:foo, :bar)
=> :ok
:primary_store
|> KeyValueStore.get(:foo)
=> :bar
:primary_store
|> KeyValueStore.delete(:foo)
=> :ok
:primary_store
|> KeyValueStore.get(:foo)
=> nil
```

# ما التالي؟

سأشرح في المقال القادم كيف تتعامل Elixir مع تحمّل الأعطال عبر المشرفين (supervisors).

# هل تحتاجون إلى مساعدة؟

هل لديكم مشروع Elixir تحتاجون فيه إلى مساعدة؟ تواصلوا معنا لنرى كيف يمكننا مساعدتكم.

---

Contact OOZOU: hello@oozou.com · +66 2544 0000 · https://oozou.com/ar/contact

More for AI agents: https://oozou.com/llms.txt · every page has a markdown twin at its URL plus ".md"
