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

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

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

ابدأ الدورة

عندما لا يكفي CRUD العادي: التعرف على CQRS و Event Sourcing

\n

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

\n

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

\n\n

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

\n

CQRS هو فصل المسؤولية بين الأوامر (الكتابة) والاستعلامات (القراءة). في CRUD التقليدي، نموذج بيانات واحد مسؤول عن كل من الإنشاء والقراءة. في CQRS، نقوم بإنشاء نموذجين منفصلين:

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

هذا يسمح بتحسين كل جانب بشكل مستقل: يمكن أن تكون الكتابة صارمة في التحقق من الصحة، بينما تكون القراءة غير معيارية وسريعة. في Go، هذا فعال بشكل خاص بفضل goroutines و channels.

\n\n

مثال بسيط على CQRS في Go

\n

لنقم بإنشاء نظام أساسي لإدارة الطلبات. لنبدأ بالأوامر:

\n\n
type CreateOrderCommand struct {\n    OrderID   string\n    UserID    string\n    ProductID string\n    Amount    float64\n}\n\ntype CommandHandler struct {\n    repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n    order := Order{\n        ID:        cmd.OrderID,\n        UserID:    cmd.UserID,\n        ProductID: cmd.ProductID,\n        Amount:    cmd.Amount,\n        Status:    "pending",\n        CreatedAt: time.Now(),\n    }\n    return h.repo.Save(ctx, order)\n}
\n\n

الآن لنقم بإنشاء نموذج منفصل للقراءة:

\n\n
type OrderQuery struct {\n    repo *ReadRepository\n}\n\ntype OrderView struct {\n    OrderID    string\n    UserName   string\n    ProductName string\n    Total      float64\n    Status     string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n    return q.repo.GetOrderView(ctx, orderID)\n}
\n\n

لاحظ: في OrderView توجد بالفعل بيانات غير معيارية (اسم المستخدم، اسم المنتج) يتم جمعها من مصادر مختلفة. هذا يسرع القراءة بعشرات المرات.

\n\n

Event Sourcing: لا نسجل الحالة، بل نسجل الأحداث

\n

يغير Event Sourcing نهج تخزين البيانات: بدلاً من تخزين الحالة الحالية للكائن، نقوم بتخزين سلسلة من الأحداث التي أدت إلى هذه الحالة. وهذا يعطي قوة خارقة: تاريخ كامل للتغييرات وإمكانية "إرجاع" الحالة إلى الوراء.

\n\n

تنفيذ Event Sourcing في Go

\n

لنبدأ بأنواع الأحداث الأساسية:

\n\n
type Event struct {\n    ID        string\n    AggregateID string\n    Type      string\n    Data      []byte\n    Timestamp time.Time\n    Version   int\n}\n\ntype EventStore interface {\n    Save(ctx context.Context, events []Event) error\n    Load(ctx context.Context, aggregateID string) ([]Event, error)\n}
\n\n

الآن لنقم بتنفيذ المجمع (Aggregate) - الكائن الذي يستعيد حالته من الأحداث:

\n\n
type OrderAggregate struct {\n    ID     string\n    Status string\n    Items  []OrderItem\n    Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n    switch event.Type {\n    case "OrderCreated":\n        var data OrderCreatedData\n        json.Unmarshal(event.Data, &data)\n        a.ID = data.OrderID\n        a.Status = "created"\n    case "OrderConfirmed":\n        a.Status = "confirmed"\n    case "OrderShipped":\n        a.Stat                

المدونات

توصيات الكتب