نمط CQRS و Event Sourcing: التنفيذ مع أمثلة كود
دليل تنفيذ CQRS و Event Sourcing الكامل مع TypeScript و Node.js. يغطي مخازن الأحداث والإسقاطات واللقطات ومتى تستحق هذه الأنماط التعقيد.
مقدمة
CQRS (فصل مسؤولية الأوامر والاستعلامات) و Event Sourcing أنماط قوية يمكنها حل مشاكل محددة بأناقة. يمكنها أيضاً إضافة تعقيد هائل عند تطبيقها بشكل غير مناسب.
بعد تطبيق هذه الأنماط في أنظمة تدقيق الرعاية الصحية ومنصات المعاملات المالية وأنظمة الامتثال الحكومية، طورت فهماً واضحاً لمتى تتألق ومتى تكون مبالغة.
فهم CQRS
CQRS يفصل عمليات القراءة والكتابة إلى نماذج مختلفة:
// النهج التقليدي: نفس النموذج للقراءات والكتابات class OrderService { async createOrder(data: CreateOrderDto): Promise<Order> { const order = new Order(data); await this.orderRepository.save(order); return order; } async getOrdersWithDetails(userId: string): Promise<OrderWithDetails[]> { // غير مريح: يحتاج لربط جداول متعددة وتحويل البيانات const orders = await this.orderRepository.findByUserId(userId); return Promise.all(orders.map(async (order) => { // ... منطق تجميع معقد })); } }
// نهج CQRS: نماذج منفصلة محسنة لغرضها // جانب الكتابة: يفرض قواعد العمل class OrderCommandHandler { async handle(command: CreateOrderCommand): Promise<void> { const order = Order.create(command); await this.orderRepository.save(order); await this.eventPublisher.publish(order.pullEvents()); } } // جانب القراءة: محسن للاستعلامات class OrderQueryHandler { async getOrdersWithDetails(userId: string): Promise<OrderDetailsView[]> { // عرض مسبق الحساب وغير طبيعي محسن لهذا الاستعلام return this.orderDetailsViewRepository.findByUserId(userId); } }
فهم Event Sourcing
Event Sourcing يخزن الحالة كتسلسل من الأحداث بدلاً من الحالة الحالية:
// التقليدي: تخزين الحالة الحالية // جدول orders: { id, status, total_amount, customer_id, updated_at } // Event Sourcing: تخزين الأحداث // جدول order_events: // { stream_id, version, event_type, event_data, timestamp } class Order extends AggregateRoot { // إعادة بناء الحالة من الأحداث static fromEvents(events: DomainEvent[]): Order { const order = new Order(); events.forEach(event => order.apply(event)); return order; } private apply(event: DomainEvent): void { switch (event.type) { case 'OrderCreated': this.id = new OrderId(event.data.orderId); this.status = OrderStatus.PENDING; break; case 'ItemAdded': this.items.push(new OrderItem(event.data)); break; // ... المزيد من الأحداث } } }
متى تستخدم CQRS
السيناريو 1: عدم تماثل القراءة/الكتابة
عندما تفوق القراءات الكتابات بشكل كبير ولها خصائص أداء مختلفة:
// كتالوج منتجات التجارة الإلكترونية // الكتابات: ~100/ساعة (تحديثات المسؤول) // القراءات: ~100,000/ساعة (تصفح العملاء)
السيناريو 2: متطلبات استعلام معقدة
عندما لا يدعم نموذج النطاق بشكل طبيعي الاستعلامات المطلوبة.
متى تستخدم Event Sourcing
السيناريو 1: متطلبات التدقيق
عندما يجب عليك شرح ما حدث بالضبط ومتى:
// نظام الرعاية الصحية: مسار تدقيق كامل مطلوب بالتنظيم class PatientMedicationHistory { getFullHistory(patientId: PatientId): MedicationEvent[] { return this.eventStore.getEvents(patientId, 'medication'); // يرجع: // [ // { type: 'MedicationPrescribed', ... }, // { type: 'MedicationDispensed', ... }, // { type: 'AdverseReactionReported', ... } // ] } }
السيناريو 2: الاستعلامات الزمنية
عندما تحتاج للإجابة "ما كانت الحالة في الوقت X؟":
class AccountBalanceService { // ما كان الرصيد في 31 ديسمبر للتقرير الضريبي؟ getBalanceAtTime(accountId: AccountId, asOf: Date): Money { const events = this.eventStore.getEventsUntil(accountId, asOf); return events.reduce((balance, event) => { switch (event.type) { case 'Deposited': return balance.add(event.amount); case 'Withdrawn': return balance.subtract(event.amount); default: return balance; } }, Money.zero()); } }
السيناريو 3: منطق عمل معقد مع إعادات التشغيل
عندما تحتاج لإصلاح الأخطاء بإعادة تشغيل الأحداث بمنطق مصحح.
متى لا تستخدم هذه الأنماط
تطبيقات CRUD البسيطة
منشورات المدونة، ملفات المستخدمين، إدارة المخزون البسيطة - CRUD جيد تماماً!
الفرق الصغيرة بدون خبرة Event Sourcing
منحنى التعلم حاد. إذا لم يستخدم فريقك هذه الأنماط من قبل، ابدأ بنهج أبسط.
عندما يكون الاتساق القوي مطلوباً
Event sourcing و CQRS يقدمان الاتساق النهائي. إذا كان نطاقك يتطلب اتساقاً فورياً عبر جميع العروض، فكر ملياً.
اعتبارات التنفيذ
اختيار مخزن الأحداث
// الخيار 1: مبني لهذا الغرض (EventStoreDB) const eventStore = new EventStoreDBClient({ endpoint: 'localhost:2113' }); // الخيار 2: البناء على قاعدة بيانات موجودة class PostgresEventStore implements EventStore { async append(streamId: string, events: DomainEvent[]): Promise<void> { await this.db.transaction(async (tx) => { // ... منطق الإلحاق }); } }
اللقطات للأداء
class OrderRepository { async getById(orderId: OrderId): Promise<Order> { // التحقق من لقطة حديثة const snapshot = await this.snapshotStore.getLatest(orderId); // تحميل الأحداث منذ اللقطة const events = snapshot ? await this.eventStore.getEventsSince(orderId, snapshot.version) : await this.eventStore.getAllEvents(orderId); // إعادة البناء من اللقطة + الأحداث const order = snapshot ? Order.fromSnapshot(snapshot.state) : new Order(); events.forEach(event => order.apply(event)); return order; } }
الخلاصة
CQRS و Event Sourcing أدوات قوية تحل مشاكل حقيقية:
- CQRS: عندما تختلف أنماط القراءة والكتابة بشكل كبير
- Event Sourcing: عندما تكون مسارات التدقيق أو الاستعلامات الزمنية أو قدرات الإعادة مطلوبة
لكنها تأتي بتكاليف:
- زيادة التعقيد
- تحديات الاتساق النهائي
- منحنى تعلم أكثر حدة
- المزيد من البنية التحتية للإدارة
ابدأ بسيطاً. أضف هذه الأنماط فقط عندما يكون لديك متطلبات واضحة تبرر تعقيدها.
مقالات ذات صلة
هندسة البرمجياتقراءة 20 دقيقة
البنية المبنية على الأحداث في أنظمة المؤسسات: الأنماط والمقايضات
دليل عملي لتطبيق البنية المبنية على الأحداث على نطاق واسع. يغطي اختيار وسيط الرسائل وتصميم مخطط الأحداث وأنماط الاتساق النهائي والدروس من أنظمة الإنتاج.
هندسة البرمجياتقراءة 25 دقيقة
سياقات DDD المحددة: دليل التنفيذ الكامل
تعلم كيفية تنفيذ السياقات المحددة في التصميم المبني على النطاق. دليل عملي يغطي خرائط السياق والتجميعات وأحداث النطاق وأمثلة حقيقية.
هندسة البرمجياتقراءة 21 دقيقة
كيفية بناء أنظمة موزعة متسامحة مع الأخطاء: أنماط Circuit Breaker و Retry و Timeout
بناء أنظمة موزعة مرنة مع circuit breakers و retries و timeouts. أنماط إنتاجية للتعامل مع الفشل والأخطاء المتتالية والحفاظ على التوفر.
تصميم الخلفيةقراءة 22 دقيقة
أنماط توسيع قواعد البيانات: دليل Sharding و Replication و Partitioning
توسيع قواعد البيانات مع sharding و replication و partitioning. يغطي أنماط توسيع PostgreSQL و MySQL و MongoDB مع أرقام أداء حقيقية.
تصميم الخلفيةقراءة 19 دقيقة
بنية قوائم الانتظار: دليل تنفيذ Redis و RabbitMQ و SQS
بناء أنظمة قوائم انتظار موثوقة مع Redis و RabbitMQ و AWS SQS. يغطي dead letter queues والتكافؤ وأنماط المعالجة الحقيقية.