تطلب منك تقنية Event Sourcing التوقف عن الكتابة فوق بياناتك. في تطبيقات CRUD التقليدية، يعني تحديث عنوان شحن المستخدم العثور على الصف، وتغيير القيمة، والتخلص من الحالة السابقة. أما Event Sourcing فيتبع مساراً مختلفاً؛ فهو يخزن كل تغيير كحقيقة غير قابلة للتغيير: مستخدم أنشأ حساباً، أو حدث عنوانه، أو تحقق من بريده الإلكتروني. لا يتم تخزين الحالة الحالية للنظام بشكل مباشر، بل يتم حسابها عن طريق إعادة تشغيل هذه الأحداث بالترتيب.
يحل هذا النمط مشكلات حقيقية. تصبح سجلات التدقيق (Audit trails) نواتج ثانوية مجانية. يمكنك إعادة بناء حالة طلب ما في أي لحظة ماضية. يمكنك تصحيح الأخطاء (Debug) عن طريق إعادة تشغيل ما حدث بالضبط. المقابل هو التعقيد؛ فأنت الآن تدير تدفقات من الحقائق، ونماذج القراءة (read models)، والاتساق النهائي (eventual consistency) بدلاً من مجرد صفوف بسيطة.
يمكن لـ PostgreSQL أن يعمل كمخزن للأحداث (event store). معظم الفرق تستخدمه بالفعل. فهو يوفر معاملات ACID، وJSONB للحمولات المرنة، وأدوات نسخ احتياطي مثبتة الكفاءة. لست بحاجة إلى إدخال Kafka أو Cassandra أو قاعدة بيانات متخصصة لتخزين الأحداث في اليوم الأول. توفر لك نسخة Postgres القياسية الضمانات المعاملاتية وسجلات التدقيق التي تتطلبها تقنية Event Sourcing دون توسيع نطاق بنيتك التحتية.
شكل مخزن أحداث Postgres
يمكن أن يكون المخطط (schema) بسيطاً لدرجة محرجة. كحد أدنى، تحتاج إلى جدول يضيف الأحداث ولا يقوم أبداً بتحديثها في مكانها. التصميم العملي يبدو كالتالي:
idكـbigserialأوUUIDليعمل كترتيب عالمي.stream_idلتجميع الأحداث ذات الصلة، مثل جميع التغييرات لمستخدم واحد أو طلب واحد.event_typeكنص عادي:UserEmailChanged،PaymentReceived،InventoryAdjusted.payloadكـJSONBيحتوي على البيانات المحددة لذلك الحدث.occurred_atمع دقة المنطقة الزمنية.versionلكل تدفق (stream)، لفرض التزامن المتفائل (optimistic concurrency).
تفرض قاعدة عدم التغيير في كود التطبيق أو باستخدام قيد في قاعدة البيانات. يمنع الفهرس الفريد (unique index) على (stream_id, version) وجود كاتبين يضيفان نفس رقم التسلسل. عندما يصل أمر ما، تقرأ الإصدار الحالي لهذا التدفق، وتزيده، وتدرج الحدث الجديد داخل معاملة (transaction). إذا سبقك معالج آخر، سيفشل القيد الفريد، وعليك حينها إعادة المحاولة أو رفض الأمر.
لنأخذ مثالاً ملموساً. أنت تدير نظام مخزون. بدلاً من صف inventory واحد مع عمود quantity ، تقوم بإضافة أحداث إلى جدول inventory_events. الحدث ItemReceived يضيف عشر وحدات. ItemReserved يخصم اثنتين. ItemShipped يخصم ثلاثاً. لمعرفة المخزون الحالي لـ SKU-42، تقوم بجمع حمولات الأحداث ذات الصلة. لمعرفة المخزون قبل ثلاثة أيام، تقوم بالجمع حتى ذلك الطابع الزمني فقط. إذا تسبب خطأ في منطق الشحن في مشكلة يوم الثلاثاء الماضي، يمكنك إعادة تشغيل الأحداث عبر كود مصحح للحصول على الحالة الحقيقية. لا يمكنك فعل ذلك باستخدام جملة UPDATE بسيطة.
مبادئ تبقيك بعيداً عن المتاعب
البناء على Postgres لا يلغي الحاجة إلى الانضباط. تنطبق المبادئ التالية مباشرة على الأنظمة القائمة على Event Sourcing.
اجعل الأمر بسيطاً. التعقيد يقتل الموثوقية. قاوم الرغبة في بناء إطار عمل أحداث عام قبل أن تطلق تدفقاً واحداً يعمل بالفعل. جدول واحد، ودالة مستودع (repository function) لإضافة الأحداث، وعامل إسقاط (projection worker) لبناء نماذج القراءة، هي أشياء كافية لإثبات القيمة. لا تضف الأدوات إلا عند ظهور مشكلة ملموسة.
ابدأ صغيراً. لا تعد كتابة نظامك المتكامل (monolith) بالكامل. اختر سياقاً محدوداً (bounded context) يكون فيه سجل التدقيق مبرراً للتكاليف الإضافية. سجل الفواتير، أو محرك سير العمل، أو نظام حجز المخزون هي مرشحات جيدة. ابنِ ذلك المسار الواحد من البداية إلى النهاية. اتركه يعمل في بيئة الإنتاج، ثم قرر ما إذا كنت ستتوسع.
حدد النجاح أولاً. تقنية Event Sourcing ليست بنية معمارية افتراضية؛ بل هي حل لاحتياجات محددة. إذا كان مطلبك هو فقط تتبع الحالة الأخيرة، فإن CRUD أسرع وأرخص. أما إذا كنت بحاجة إلى استعلامات زمنية، أو قابلية تدقيق صارمة، أو القدرة على إعادة بناء نماذج القراءة عند الطلب، فإن الأحداث هي الخيار المنطقي. اعرف المشكلة التي تحلها قبل أن تلتزم بالحل.
قس قبل أن تحسن. يمكن لـ PostgreSQL الحديثة على أجهزة متواضعة استيعاب آلاف الأحداث في الثانية باستخدام جدول بسيط يعتمد على الإضافة فقط (append-only). لا تقم بتقسيم (shard) مخزن الأحداث الخاص بك أو إدخال مخططات تقسيم معقدة حتى يثبت نظام المراقبة لديك أنك استنفدت الحلول الأبسط. قم بفهرسة الحقول التي تستعلم عنها. اضبط autovacuum لأعباء العمل التي تعتمد على الإضافة فقط. ثم قم بالقياس مرة أخرى.
Test everything. Unit test your event handlers. Integration test the append path. Most importantly, test failure scenarios. What happens when two nodes append to the same stream simultaneously? What happens when a projection worker crashes mid-batch? Write tests that verify your optimistic concurrency and your at-least-once delivery guarantees.
Monitor in production. The events table will grow. Unlike a normalized schema where updates keep row counts flat, event sourcing is intentionally additive. Track table size, disk I/O, and the lag between your write model and your read model projections. Set alerts on projection lag before your users notice stale data.
Automate manual tasks. Manual schema changes, manual projection rebuilds, and manual event replay are ticking time bombs. Script your migration strategy. If you evolve an event schema, automate the upcasting or transformation so that old events can be replayed through new logic without human intervention at midnight.
Document your choices. Write down why specific streams exist, what each event type means, and when the team should choose CRUD over events. Event sourcing introduces cognitive load. Good documentation prevents a new engineer from guessing wrong and appending malformed events to a critical stream.
Traps That Waste Months
Event sourcing has a way of sounding elegant in a diagram and painful in production. Watch for these traps.
Underestimating complexity. Replaying events to rebuild state is conceptually simple. Managing idempotency, snapshotting for performance, and compensating transactions across aggregates is not. Break your system into small pieces. Solve one stream at a time.
Over-engineering. Do not provision a multi-node Kafka cluster because you imagine your event volume will one day require it. Postgres can carry you surprisingly far. Introduce new infrastructure only when you have a measured bottleneck that you cannot fix within your current setup.
Ignoring technical debt. Old event schemas linger forever. If you change your OrderCreated payload, you still have ten million historical events in the old shape. Track this debt. Plan backward-compatible readers or migration scripts. Do not let the burden of legacy events slow every new feature.
Choosing tools the team cannot run. The best architecture fails if only one person understands it. If your team knows Postgres and SQL, start there. If you introduce a specialized event store, make sure you have the operational expertise to debug it at two in the morning.
A Practical Starting Point
If this approach fits your problem, do not wait for a five-quarter rewrite. Start this week.
Audit your current systems. Find a place where an audit trail would solve real pain. Maybe it is an order state machine that currently maintains a single status column. Perhaps it is a financial ledger where balance corrections require manual database patches. Pick one gap where overwriting state has hurt you.
Then pick one small improvement you can make today. Create one event table. Model one stream. Write one projection that builds a read model from those events. Deploy it behind a feature flag. Watch it handle real traffic.
Event sourcing with PostgreSQL is not magic. It is a practical tool for teams that need to know not just where things are, but how they got there. Build slowly, measure honestly, and let your actual requirements guide the architecture.
