CQRS و Event Sourcing في TypeScript: بنية الأنظمة المعقدة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا تتوقف تطبيقات CRUD العادية عن العمل عندما يصبح العمل معقدًا

\n\n

هل سبق لك أن واجهت موقفًا تتحول فيه مهمة بسيطة - "إظهار تاريخ تغييرات الطلب" - إلى جحيم؟ تتصفح قاعدة البيانات، تنظر إلى السجلات، تحاول استعادة من ومتى غيّر الحالة. ثم يأتي طلب: "قم بتدقيق جميع إجراءات المستخدم خلال السنوات الثلاث الماضية". وتدرك - أن البنية الحالية لن تتحمل ذلك.

\n\n

مرحبًا، اسمي PythonLib، واليوم سنشرح نمطين قويين ينقذان في مثل هذه المواقف: CQRS (فصل مسؤوليات الأوامر والاستعلامات) و Event Sourcing. سنكتب كودًا بلغة TypeScript يمكن استخدامه فورًا في مشروع حقيقي.

\n\n

إذا كنت تطور أنظمة fintech، أو الأسواق الإلكترونية، أو CRM، أو أي أنظمة تكون فيها التاريخ والتدقيق مهمين - فهذه المقالة لك.

\n\n

ما هو CQRS بكلمات بسيطة

\n\n

تخيل مكتبة. عادةً، أمين مكتبة واحد يقوم بإعارة الكتب، واستلامها، وحفظ السجلات. ولكن عندما يزداد عدد الزوار - يتشكل طابور. يقترح CQRS فصل المسؤوليات: أمين مكتبة واحد فقط يعير (أوامر)، والثاني فقط يستلم ويسجل (استعلامات).

\n\n

في البرمجة، هذا يعني فصل نماذج القراءة والكتابة. بدلاً من مستودع واحد متكامل، ننشئ:

\n\n
    \n
  • نموذج الأوامر (Command Model) - لتغيير البيانات (إنشاء، تحديث، حذف)
  • \n
  • نموذج الاستعلامات (Query Model) - لقراءة البيانات (قراءة)
  • \n
\n\n

يبدو هذا تعقيدًا؟ نعم، في البداية. ولكن عندما ينمو النظام، تحصل على مرونة: يمكن تحسين القراءة بشكل منفصل عن الكتابة، واستخدام قواعد بيانات مختلفة، والتخزين المؤقت.

\n\n

مثال باستخدام TypeScript: CQRS بسيط بدون Event Sourcing

\n\n

لنفترض أن لدينا نظام إدارة طلبات. أولاً، نحدد الكيانات:

\n\n
// domain/order.ts\nexport interface Order {\n  id: string;\n  userId: string;\n  items: OrderItem[];\n  status: 'pending' | 'confirmed' | 'shipped' | 'cancelled';\n  total: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nexport interface OrderItem {\n  productId: string;\n  quantity: number;\n  price: number;\n}\n
\n\n

الآن ننشئ أمرًا واستعلامًا:

\n\n
// application/commands/create-order.command.ts\nexport class CreateOrderCommand {\n  constructor(\n    public readonly userId: string,\n    public readonly items: OrderItem[]\n  ) {}\n}\n\n// application/commands/create-order.handler.ts\nimport { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { OrderRepository } from '../repositories/order.repository';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n  constructor(private readonly orderRepo: OrderRepository) {}\n\n  async execute(command: CreateOrderCommand): Promise<string> {\n    const order: Order = {\n      id: uuid(),\n      userId: command.userId,\n      items: command.items,\n      status: 'pending',\n      total: command.items.reduce((sum, item) => sum + item.price * item.quantity, 0),\n      createdAt: new Date(),\n      updatedAt: new Date()\n    };\n    await this.orderRepo.save(order);\n    return order.id;\n  }\n}\n
\n\n

وللقراءة - معالج منفصل:

\n\n
// application/queries/get-order.query.ts\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n\n// application/queries/get-order.handler.ts\nimport { QueryHandler, IQueryHandler } from '@nestjs/cqrs';\nimport { OrderReadRepository } from '../repositories/order-read.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n  constructor(private readonly readRepo: OrderReadRepository) {}\n\n  async execute(query: GetOrderQuery): Promise<Order | null> {\n    return this.readRepo.findById(query.orderId);\n  }\n}\n                

المدونات

توصيات الكتب