غالبًا ما يُمدح التصميم الموجّه بالنطاق (DDD) لوضوحه الاستراتيجي، لكنه قد يفشل إذا كانت التنفيذ التكتيكي فوضويًا. أحد أهم المكونات، والتي غالبًا ما تُدار بشكل خاطئ، هو حدث النطاق. عند تنفيذه بشكل صحيح، تتيح أحداث النطاق لسياقات محدودة مختلفة أن تستجيب للتغييرات دون اقتران وثيق. عند التنفيذ بشكل سيئ، تخلق اعتماديات خفية وأنظمة هشة.
في هذه المقالة، سنحلل الركائز الثلاث لتصميم أحداث النطاق الفعّالة: معايير التسمية، وهيكل الحمولة، واستراتيجيات الفصل.
فن تسمية الأحداث
الأسماء هي واجهة برمجيتك (API). في DDD، يجب أن توضح أسماء الأحداث ما الذي حدث ومتى. خطأ شائع هو استخدام الأفعال الأمرية أو الأسماء الغامضة.
أفضل الممارسات:
- استخدم الماضي: الأحداث تصف أشياء حدثت بالفعل. OrderPlaced (تم وضع الطلب)، وليس PlaceOrder (ضع الطلب).
- كن محددًا: PaymentFailed (فشل الدفع) أفضل من PaymentError (خطأ في الدفع). التحديد يقلل من التفسير لدى المشتركين.
- شمل جذع التجميع (Aggregate Root): أضف بادئة أو ادمج اسم التجميع إذا لم يكن السياق واضحًا. InvoiceGenerated (تم إنشاء الفاتورة) مقابل InvoiceApproved (تمت الموافقة على الفاتورة).
تجنب الأسماء العامة مثل EntityUpdated (تم تحديث الكيان). هذا يجبر المستهلكين على فحص الحمولة لفهم أهمية التغيير، مما ينتهك مبدأ التغليف (Encapsulation).
هيكل الحمولة: البيانات القابلة للتنفيذ الحد الأدنى
نمط معاكس شائع هو إغراق حمولة الحدث بحالة الكيان بأكملها. هذا يخلق مشكلتين:
- تسرب المعلومات: يتلقى المشتركون بيانات لا يحتاجونها، مما يكشف عن التفاصيل الداخلية للتجميع.
- الهشاشة: إذا قمت بتغيير بنية الكيان، فإنك تكسر كل مستهلك، حتى أولئك الذين كانوا يهتمون بحقل واحد فقط.
قاعدة "الحمولة القابلة للتنفيذ الحد الأدنى"
شمل فقط البيانات اللازمة للمشترك لاتخاذ إجراء. إذا احتاج المشترك إلى المزيد، فيجب أن يستعلم من مستودعه الخاص أو يقوم باستدعاء API منفصل.
// ❌ سيئ: يفرغ كل شيء
public class OrderCreatedEvent {
public Order Order { get; set; } // يحتوي على 50 حقلًا، معظمها غير ذي صلة
}
// ✅ جيد: فقط ما هو مطلوب
public class OrderCreatedEvent {
public Guid OrderId { get; }
public Guid CustomerId { get; }
public DateTime CreatedAtUtc { get; }
public decimal TotalAmount { get; }
public Currency Code { get; }
}
لاحظ كيف يتضمن OrderCreatedEvent فقط الحقول اللازمة لأنظمة المصب (مثل المخزون، والفوترة) للاستجابة. قد يحتاج خدمة الشحن فقط إلى OrderId وCustomerId للبحث عن العنوان لاحقًا. لا يحتاج إلى TotalAmount.
الفصل: الهدف الرئيسي
الهدف الرئيسي من أحداث النطاق هو الاقتران الضعيف. إذا كان الناشر يعرف من هم المشتركون، فأنت لم تفصل. لقد أضفت فقط طبقة من التحويل (Indirection).
استراتيجيات للفصل الحقيقي:
- النشر عبر تجريد: يجب ألا يستدعي جذع التجميع الخدمات مباشرة. بدلاً من ذلك، يكشف عن الأحداث، ووسيط (Mediator) أو ناقل أحداث (Event Bus) ينشرها.
public class Order { private readonly List_events = new List (); public void Place() { if (Status != Status.Pending) throw new DomainException("Order not pending"); Status = Status.Placed; _events.Add(new OrderPlacedEvent(OrderId, CustomerId, TotalAmount, DateTime.UtcNow)); } public List GetUncommittedEvents() => _events; } - فصل أحداث التكامل عن أحداث النطاق:
- أحداث النطاق: تُستخدم داخل نفس السياق المحدود أو للتماسك الداخلي. قد تكون متزامنة أو غير متزامنة لكنها مقترنة ارتباطًا وثيقًا بمنطق النطاق.
- أحداث التكامل: تُستخدم للتواصل عبر السياقات المحدودة. يجب أن تكون هذه رسائل اتساق نهائي (Eventual Consistency). يجب أن تكون مستقرة، ومُرقّمة الإصدار، وتُنقل عبر وسيط (Kafka, RabbitMQ, إلخ).
لا تنشر أحداث النطاق الداخلية الخاصة بك مباشرةً إلى وسيط الرسائل. قم بتحويلها إلى أحداث تكامل أولاً. هذا يسمح لك بإعادة هيكلة نطاقك الداخلي دون كسر العقود الخارجية.
معالجة الإصدارات والتوافق العكسي
الأحداث هي رسائل طويلة العمر. بمجرد نشرها، قد تستهلكها أنظمة لا تتحكم بها. عامل مخططات الأحداث مثل واجهات برمجة التطبيقات العامة.
- لا تحذف الحقول أبدًا: أضف حقولاً جديدة بدلاً من ذلك. يمكن للمستهلكين تجاهل الحقول المجهولة.
- رقّم أحداثك: إذا كان التغيير الكاسر ضروريًا، أنشئ إصدارًا جديدًا للحدث (مثل
OrderPlacedV2) وشغّل كلاهما بالتوازي لفترة انتقالية. - استخدم سجلات المخططات (Schema Registries): أدوات مثل Confluent Schema Registry تفرض فحوصات التوافق قبل النشر.
الخلاصة
أحداث النطاق هي أداة قوية لبناء أنظمة قابلة للتوسع ومفصولة في DDD. ومع ذلك، يأتي قوتها مع المسؤولية. من خلال الالتزام بمعايير تسمية واضحة، وإبقاء الأحمال في الحد الأدنى ومركزة، والفصل الصارم بين أحداث النطاق وأحداث التكامل، يمكنك بناء أنظمة مرنة تجاه التغيير وسهلة التطور.
تذكر: إذا احتاج مشتركونك إلى معرفة كيف تم إنتاج الحدث، فقد تسربت تفاصيل التنفيذ. إذا احتاجوا فقط إلى معرفة ما الذي حدث وما الذي يجب عليهم فعله، فقد صممت حدثًا جيدًا.
برمجة سعيدة!