أحداث النطاق وخدمات النطاق
سجّل حقائق الأعمال باستخدام أحداث النطاق وخدماته.
أحداث النطاق وخدمات النطاق درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
التقاط وقائع الأعمال
لا يناسب كل سلوك كيانًا واحدًا. تلتقط أحداث النطاق وقائع الأعمال المهمة التي حدثت بالفعل، مثل OrderPlaced، مما يتيح لأجزاء أخرى من النظام التفاعل معها. أما خدمات النطاق فتحمل منطق النطاق الذي لا ينتمي بطبيعته إلى كيان أو كائن قيمة واحد. يوضّح هذا الدرس كيف يحافظ كلا النمطين على تعبيرية النموذج دون تضخيم التجميعات.
ما هو حدث النطاق
حدث النطاق هو سجل غير قابل للتغيير لشيء حدث في النطاق، وتُسمّى الأحداث بصيغة الماضي باستخدام اللغة المشتركة، مثل: OrderPlaced وPaymentReceived وCustomerRelocated. ويحمل البيانات التي تصف الواقعة، بالإضافة إلى وقت حدوثها. وهو كائن قيمة: يُنشأ مرة واحدة ولا يتغير أبدًا.
<?php
final class OrderPlaced {
public function __construct(
public readonly string $orderId,
public readonly int $totalCents,
public readonly DateTimeImmutable $occurredOn = new DateTimeImmutable()
) {}
}
$e = new OrderPlaced('o1', 1300);
echo $e->orderId, ' @ ', $e->occurredOn->format('H:i'), PHP_EOL;
تسجّل التجميعات الأحداث
يسجّل جذر التجميع الأحداث كجزء من سلوكه، ثم يتيح الوصول إليها حتى تتمكن طبقة التطبيق من إرسالها بعد إتمام المعاملة. ولا يرسل التجميع الأحداث بنفسه، بل يسجّلها فقط. ويحافظ ذلك على خلو النطاق من مخاوف البنية التحتية.
<?php
final class OrderPlaced {
public function __construct(public readonly string $orderId) {}
}
abstract class AggregateRoot {
private array $events = [];
protected function record(object $e): void { $this->events[] = $e; }
public function releaseEvents(): array {
$out = $this->events; $this->events = []; return $out;
}
}
final class Order extends AggregateRoot {
public function __construct(public readonly string $id) {
$this->record(new OrderPlaced($id));
}
}
$o = new Order('o1');
foreach ($o->releaseEvents() as $e) { echo get_class($e), PHP_EOL; }
الإرسال بعد إتمام المعاملة
يخزّن التطبيق التجميع، ثم يُتم المعاملة، وبعد ذلك يرسل الأحداث المُطلَقة. ويضمن الإرسال بعد إتمام المعاملة ألا تتفاعل المعالجات أبدًا مع واقعة أُلغيت معاملتها. يمثّل الترتيب أدناه القالب الآمن للآثار الجانبية، مثل رسائل البريد الإلكتروني أو الاستدعاءات الخارجية.
<?php
final class PlaceOrderService {
public function __construct(
private object $repo,
private object $dispatcher
) {}
public function handle(object $order): void {
// 1. persist inside a transaction
$this->repo->save($order); // commit happens here
// 2. only AFTER commit, release and dispatch
foreach ($order->releaseEvents() as $event) {
$this->dispatcher->dispatch($event);
}
}
}
echo "order persisted, then events dispatched", PHP_EOL;
تستجيب المعالجات
يمكن أن يكون لكل حدث عدد من المعالجات، منفصلة عن التجميع الذي أطلقه. ويوجّه موزّع PSR-14 الحدث إلى المستمعين المسجّلين. وتعني إضافة استجابة جديدة، مثل التحليلات أو الإشعارات، إضافة مستمع، دون تعديل التجميع أبدًا.
<?php
final class OrderPlaced { public function __construct(public readonly string $orderId) {} }
final class Dispatcher {
private array $listeners = [];
public function on(string $event, callable $l): void { $this->listeners[$event][] = $l; }
public function dispatch(object $event): void {
foreach ($this->listeners[$event::class] ?? [] as $l) { $l($event); }
}
}
$d = new Dispatcher();
$d->on(OrderPlaced::class, fn($e) => print("email for {$e->orderId}\n"));
$d->on(OrderPlaced::class, fn($e) => print("analytics for {$e->orderId}\n"));
$d->dispatch(new OrderPlaced('o1'));
الأحداث عبر الحدود
تفصل أحداث النطاق بين التجميعات التي يجب أن تظل متسقة دون مشاركة معاملة واحدة. فعلى سبيل المثال، يمكن لـ OrderPlaced في سياق المبيعات تشغيل حجز المخزون في سياق آخر عبر حدث. وتكون هذه الأحداث كائنات داخل النظام، لكنها غالبًا ما تصبح أحداث تكامل على وسيط رسائل عند عبورها بين الخدمات. أبقِ أحداث النطاق الداخلية وأحداث التكامل الخارجية منفصلة؛ فلا ينبغي أن يكون العقد العام هو نموذجك الداخلي.
ما هي خدمة النطاق
تحمل خدمة النطاق منطق النطاق الذي:
- يتضمن عدة تجميعات أو كيانات، ولذلك لا يناسب أيًا منها، و
- يمثل مفهومًا حقيقيًا من مفاهيم النطاق، وتعبّر عنه اللغة المشتركة.
وتكون عديمة الحالة وتعمل على كائنات النطاق. ومن الأمثلة التقليدية خدمة FundsTransfer التي تخصم من حساب وتضيف إلى حساب آخر.
خدمة نطاق في الشيفرة
تنسّق الخدمة بين تجميعين، لكنها لا تحتفظ بأي حالة خاصة بها. وبما أن المنطق لا ينتمي إلى أي من الحسابين، فإنه يعيش في خدمة تُسمّى باسم عملية النطاق.
<?php
final class Account {
public function __construct(public readonly string $id, private int $cents) {}
public function withdraw(int $c): void {
if ($c > $this->cents) { throw new DomainException('insufficient funds'); }
$this->cents -= $c;
}
public function deposit(int $c): void { $this->cents += $c; }
public function balance(): int { return $this->cents; }
}
final class MoneyTransferService {
public function transfer(Account $from, Account $to, int $cents): void {
$from->withdraw($cents);
$to->deposit($cents);
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new MoneyTransferService())->transfer($a, $b, 400);
echo $a->balance(), '/', $b->balance(), PHP_EOL; // 600/400
خدمة النطاق مقابل خدمة التطبيق
لا تخلط بينهما:
- تحتوي خدمة النطاق على منطق الأعمال وتعيش في طبقة النطاق؛ ولا تعرف شيئًا عن المعاملات أو HTTP.
- تنسّق خدمة التطبيق حالة استخدام: إذ تحمّل التجميعات من المستودعات، وتستدعي منطق النطاق، وتُتم المعاملة، وترسل الأحداث. وهي منسّق رفيع لا يملك قواعد أعمال خاصة به.
تجنّب فخ الخدمات
من السهل الإفراط في استخدام خدمات النطاق. فإذا أمكن وضع المنطق على كيان أو كائن قيمة، فينبغي وضعه هناك؛ فهذه هي الطريقة لتجنب النموذج الفقير. لا تلجأ إلى خدمة نطاق إلا عندما تمتد العملية فعلًا عبر عدة تجميعات أو لا يكون لها موضع طبيعي. وإلا فستنتزع السلوك من كياناتك، وتعيد إنشاء شيفرة إجرائية.
تنسيق خدمة التطبيق
بتجميع الأجزاء: تحمّل خدمة التطبيق الرقيقة التجميعات من المستودعات، وتستدعي خدمة المجال لتنفيذ المنطق العابر للتجميعات، وتحفظ النتيجة، ثم ترسل الأحداث. لا تحتوي إلا على تنسيق سير العمل — ولا تملك أي قواعد أعمال خاصة بها.
<?php
final class Account {
public function __construct(public readonly string $id, public int $cents) {}
}
final class MoneyTransferService { // domain service: the rule
public function transfer(Account $from, Account $to, int $c): void {
if ($c > $from->cents) { throw new DomainException('insufficient'); }
$from->cents -= $c; $to->cents += $c;
}
}
final class TransferApp { // application service: orchestration only
public function __construct(private MoneyTransferService $domain) {}
public function run(Account $a, Account $b, int $c): void {
$this->domain->transfer($a, $b, $c); // then repo->save + dispatch
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new TransferApp(new MoneyTransferService()))->run($a, $b, 250);
echo $a->cents, '/', $b->cents, PHP_EOL; // 750/250
اختبار سريع
الأحداث والخدمات.
مراجعة
لقد سجّلتم حقائق الأعمال والمنطق الذي لا موضع له. أحداث المجال سجلات غير قابلة للتغيير بصيغة الماضي، تقوم التجميعات بتسجيلها، ثم تقوم طبقة التطبيق بإرسالها بعد الإيداع، مما يفصل ردود الفعل ويمكّنها من عبور حدود التجميعات. تحتفظ خدمات المجال بمنطق مجال عديم الحالة يمتد عبر عدة تجميعات، وهي تختلف عن خدمات التطبيق الرقيقة التي تكتفي بتنسيق حالات الاستخدام. استخدموا كليهما باعتدال، بحيث يبقى السلوك في الكيانات وكائنات القيمة حيثما كان موضعه الصحيح.
الأسئلة الشائعة
هل درس «أحداث النطاق وخدمات النطاق» مجاني؟
نعم — نص درس «أحداث النطاق وخدمات النطاق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «أحداث النطاق وخدمات النطاق»؟
سجّل حقائق الأعمال باستخدام أحداث النطاق وخدماته. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «أحداث النطاق وخدمات النطاق»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لبنات DDD الأساسية: الكيانات وكائنات القيمة
- التجميعات والمستودعات والمصانع
- أحداث النطاق وخدمات النطاق
- السياقات المحددة ورسم خرائط السياق