System Design

إتقان Event Sourcing: بناء أنظمة قابلة للتدقيق وقابلة للتوسع

في مجال تصميم الأنظمة الحديثة، غالباً ما يفشل نموذج CRUD التقليدي (إنشاء، قراءة، تحديث، حذف) عند التعامل مع النطاقات التجارية المعقدة، أو الامتثال التنظيمي الصارم، أو متطلبات البيانات عالية الحجم. هنا يبرز Event Sourcing. من خلال معالجة حالة التطبيق على أنها سلسلة من الأحداث الثابتة، يوفر Event Sourcing بديلاً قوياً يمنح سجلات تدقيق كاملة، وقدرات استعلام زمنية، وقابلية توسع محسنة.

ما هو Event Sourcing؟

في جوهره، Event Sourcing هو نمط معماري حيث تُخزن التغييرات في حالة التطبيق على شكل تسلسل من الأحداث. بدلاً من تخزين الحالة الحالية للبيانات فقط (مثل عنوان العميل)، تقوم بتخزين كل حدث غيّر تلك الحالة (على سبيل المثال، "تم تحديث العنوان"، "تم إنشاء العنوان"). لإعادة بناء الحالة الحالية، يقوم النظام ببساطة بتشغيل هذه الأحداث من البداية.

يتناقض هذا النهج بشدة مع طبقات التخزين التقليدية. في قاعدة بيانات علائقية قياسية، قد يبدو تحديث ملف تعريف المستخدم كاستعلام UPDATE في SQL يقوم بكتابة البيانات السابقة فوقها. في Event Sourcing، يولد هذا الإجراء نفسه حدثاً جديداً باسم "تم تحديث ملف تعريف المستخدم"، تاركاً السجل التاريخي سليماً.

الفوائد الرئيسية للأنظمة المؤسسية

اعتماد Event Sourcing ليس مجرد خيار تقني؛ بل هو مُمكّن للأعمال. إليك المزايا الأساسية:

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

تنفيذ Event Sourcing: مثال عملي

لننظر في كيفية هيكلة متجر الأحداث (Event Store) في Java. بينما ستستخدم عادةً قاعدة بيانات مخصصة لتخزين الأحداث (مثل EventStoreDB أو Apache Kafka)، فإن النموذج المفاهيمي يبقى كما هو: مستودع للأحداث.


public class OrderAggregate {
    private List events = new ArrayList<>();
    private BigDecimal totalAmount;
    private String status;

    // إعادة تشغيل الأحداث لإعادة بناء الحالة
    public void replay(List historicalEvents) {
        historicalEvents.forEach(this::apply);
    }

    // تطبيق حدث جديد على الحالة الحالية
    public void apply(OrderEvent event) {
        if (event instanceof OrderCreatedEvent) {
            this.totalAmount = ((OrderCreatedEvent) event).getAmount();
            this.status = "CREATED";
        } else if (event instanceof OrderCancelledEvent) {
            this.status = "CANCELLED";
        }
        // إضافة إلى المخزن المؤقت المحلي ليتم حفظه
        events.add(event);
    }

    // حفظ الأحداث المعلقة في متجر الأحداث
    public void persist() {
        eventStore.save(events);
        events.clear();
    }
}

في هذا المثال، لا يخزن OrderAggregate حالته في متغيرات دائمة فقط. بدلاً من ذلك، يحافظ على قائمة بالأحداث غير الملتزمة. عند اكتمال المعاملة، يتم حفظ هذه الأحداث في متجر الأحداث. لتحميل الكائن لاحقاً، يجلب النظام جميع الأحداث المرتبطة بمعرف الطلب ويعيد تشغيلها باستخدام طريقة apply.

التحديات والاعتبارات

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

الخاتمة

Event Sourcing ليس حلاً سحرياً، ولكنه أداة تحويلية لحالات استخدام محددة. إذا كان نطاقك يعتمد بشكل كبير على المنطق التجاري المعقد، ويتطلب تدقيقاً صارماً، أو يحتاج إلى دعم تحليل البيانات التاريخية، فإن Event Sourcing يوفر الأساس المعماري لبناء أنظمة مرنة وقابلة للتوسع وشفافة. من خلال احتضان "قصة" بياناتك بدلاً من لقطة حالتها الحالية فقط، تفتح إمكانيات جديدة لتصميم البرمجيات المؤسسية.

Share: