ایونٹ سورسنگ (Event sourcing) آپ سے کہتی ہے کہ آپ اپنے ڈیٹا کو اوور رائٹ (overwrite) کرنا بند کر دیں۔ ایک روایتی CRUD ایپلی کیشن میں، صارف کے شپنگ ایڈریس کو اپ ڈیٹ کرنے کا مطلب ہے اس رو (row) کو تلاش کرنا، ویلیو کو تبدیل کرنا، اور پچھلی حالت (state) کو ختم کر دینا۔ ایونٹ سورسنگ ایک مختلف راستہ اختیار کرتی ہے۔ یہ ہر تبدیلی کو ایک غیر متبدل حقیقت (immutable fact) کے طور پر محفوظ کرتی ہے: جیسے کہ صارف نے اکاؤنٹ بنایا، اپنا پتہ اپ ڈیٹ کیا، یا اپنا ای میل ویریفائی کیا۔ سسٹم کی موجودہ حالت براہ راست محفوظ نہیں کی جاتی، بلکہ ان ایونٹس کو ترتیب وار دوبارہ چلا کر (replaying) اس کا حساب لگایا جاتا ہے۔
یہ پیٹرن حقیقی مسائل حل کرتا ہے۔ آڈٹ ٹریلز (audit trails) اس کے مفت ضمنی نتائج (byproducts) بن جاتے ہیں۔ آپ ماضی کے کسی بھی لمحے میں کسی آرڈر کی حالت کو دوبارہ تعمیر کر سکتے ہیں۔ آپ بالکل وہی چیز دوبارہ چلا کر ڈی بگ (debug) کر سکتے ہیں جو ہوا تھا۔ اس کا نقصان پیچیدگی ہے۔ اب آپ سادہ روز (rows) کے بجائے حقائق کے اسٹریمز، ریڈ ماڈلز (read models)، اور ایونچوئل کنسسٹنسی (eventual consistency) کو مینیج کرتے ہیں۔
PostgreSQL آپ کے ایونٹ اسٹور کے طور پر کام کر سکتا ہے۔ زیادہ تر ٹیمیں پہلے ہی اسے استعمال کر رہی ہوتی ہیں۔ یہ ACID ٹرانزیکشنز، لچکدار پے لوڈز کے لیے JSONB، اور آزمودہ بیک اپ ٹولز فراہم کرتا ہے۔ آپ کو پہلے دن سے ہی Kafka، Cassandra، یا کسی مخصوص ایونٹ اسٹور ڈیٹا بیس کو متعارف کروانے کی ضرورت نہیں ہے۔ ایک معیاری Postgres instance آپ کو وہ ٹرانزیکشنل ضمانتیں اور آڈٹ ٹریلز فراہم کرتا ہے جن کا ایونٹ سورسنگ تقاضا کرتی ہے، بغیر آپ کے انفراسٹرکچر کے پھیلاؤ کے۔
ایک Postgres ایونٹ اسٹور کی ساخت
اسکیمہ (schema) تقریباً حیران کن حد تک سادہ ہو سکتا ہے۔ کم از کم، آپ کو ایک ایسی ٹیبل کی ضرورت ہے جو ایونٹس کو شامل (append) کرے اور انہیں کبھی بھی اپنی جگہ پر اپ ڈیٹ نہ کرے۔ ایک عملی ڈیزائن کچھ اس طرح نظر آتا ہے:
idبطور bigserial یا UUID، جو عالمی ترتیب (global ordering) کے طور پر کام کرتا ہے۔stream_idمتعلقہ ایونٹس کو گروپ کرنے کے لیے، جیسے کہ ایک ہی صارف یا آرڈر کے تمام بدلاؤ۔event_typeسادہ ٹیکسٹ کے طور پر:UserEmailChanged,PaymentReceived,InventoryAdjusted۔payloadبطور JSONB، جو اس واقعے کا مخصوص ڈیٹا رکھتا ہے۔occurred_atٹائم زون کی درستگی کے ساتھ۔- ہر اسٹریم کے لیے
version، جو آپٹیمسٹک کنکرنسی (optimistic concurrency) کو نافذ کرتا ہے۔
آپ ایپلی کیشن کوڈ میں یا ڈیٹا بیس کنسٹرینٹ (constraint) کے ذریعے غیر متبدل ہونے کے اصول کو نافذ کرتے ہیں۔ (stream_id, version) پر ایک منفرد انڈیکس (unique index) دو رائٹرز کو ایک ہی سیکوئنس نمبر شامل کرنے سے روکتا ہے۔ جب کوئی کمانڈ آتی ہے، تو آپ اس اسٹریم کے لیے موجودہ ورژن پڑھتے ہیں، اسے بڑھاتے ہیں، اور ایک ٹرانزیکشن کے اندر نیا ایونٹ درج کرتے ہیں۔ اگر کوئی دوسرا عمل آپ سے پہلے یہ کر لے، تو منفرد کنسٹرینٹ فیل ہو جاتا ہے، اور آپ کمانڈ کو دوبارہ کوشش کرتے ہیں یا مسترد کر دیتے ہیں۔
ایک ٹھوس مثال پر غور کریں۔ آپ ایک انوینٹری سسٹم چلا رہے ہیں۔ ایک quantity کالم والی واحد inventory رو کے بجائے، آپ inventory_events ٹیبل میں ایونٹس شامل کرتے ہیں۔ ItemReceived دس یونٹس کا اضافہ کرتا ہے۔ ItemReserved دو یونٹس کم کرتا ہے۔ ItemShipped تین یونٹس کم کرتا ہے۔ SKU-42 کے لیے موجودہ اسٹاک جاننے کے لیے، آپ متعلقہ ایونٹ پے لوڈز کا مجموعہ کرتے ہیں۔ تین دن پہلے کا اسٹاک جاننے کے لیے، آپ صرف اس ٹائم اسٹیمپ تک کا مجموعہ کرتے ہیں۔ اگر گزشتہ منگل کو آپ کے شپنگ لاجک میں کسی بگ (bug) کی وجہ سے غلطی ہوئی تھی، تو آپ اصل حالت حاصل کرنے کے لیے درست کوڈ کے ذریعے ایونٹس کو دوبارہ چلا سکتے ہیں۔ آپ ایک سادہ UPDATE اسٹیٹمنٹ کے ساتھ ایسا نہیں کر سکتے۔
وہ اصول جو آپ کو مشکل سے بچاتے ہیں
Postgres پر تعمیر کرنے کا مطلب یہ نہیں کہ نظم و ضبط کی ضرورت ختم ہو گئی ہے۔ درج ذیل اصول براہ راست ایونٹ سورسڈ سسٹمز پر لاگو ہوتے ہیں۔
اسے سادہ رکھیں۔ پیچیدگی بھروسہ مندی کو ختم کر دیتی ہے۔ ایک کام کرنے والا فلو (flow) مکمل کرنے سے پہلے ایک عام ایونٹ فریم ورک بنانے کی خواہش سے بچیں۔ ایک واحد ٹیبل، ایونٹس شامل کرنے کے لیے ایک ریپوزٹری فنکشن، اور ریڈ ماڈلز بنانے کے لیے ایک پروجیکشن ورکر (projection worker) اہمیت ثابت کرنے کے لیے کافی ہیں۔ ٹولز صرف اس وقت شامل کریں جب کوئی ٹھوس مسئلہ سامنے آئے۔
چھوٹے پیمانے سے شروع کریں۔ اپنے پورے مونو لیتھ (monolith) کو دوبارہ نہ لکھیں۔ ایک ایسا باؤنڈڈ کانٹیکسٹ (bounded context) منتخب کریں جہاں آڈٹ ٹریل کا فائدہ اس کے اضافی بوجھ سے زیادہ ہو۔ بلنگ لیجر، ورک فلو انجن، یا انوینٹری ریزرویشن سسٹم اچھے امیدوار ہیں۔ اس ایک پائپ لائن کو شروع سے آخر تک بنائیں۔ اسے پروڈکشن میں چلنے دیں۔ پھر فیصلہ کریں کہ آیا اسے وسعت دینی ہے۔
پہلے کامیابی کی تعریف کریں۔ ایونٹ سورسنگ کوئی ڈیفالٹ آرکیٹیکچر نہیں ہے؛ یہ مخصوص ضروریات کا ایک حل ہے۔ اگر آپ کی ضرورت صرف تازہ ترین حالت کو ٹریک کرنا ہے، تو CRUD زیادہ تیز اور سستا ہے۔ اگر آپ کو ٹیمپورل کوئریز (temporal queries)، سخت آڈٹ ایبلٹی، یا ضرورت پڑنے پر ریڈ ماڈلز کو دوبارہ بنانے کی صلاحیت چاہیے، تو ایونٹس کا استعمال منطقی ہے۔ کسی بھی چیز کے لیے خود کو وقف کرنے سے پہلے جان لیں کہ آپ کون سا مسئلہ حل کر رہے ہیں۔
آپٹیمائز کرنے سے پہلے پیمائش کریں۔ معمولی ہارڈ ویئر پر جدید PostgreSQL ایک سادہ اپینڈ-اونلی (append-only) ٹیبل کے ساتھ فی سیکنڈ ہزاروں ایونٹس جذب کر سکتا ہے۔ اپنے ایونٹ اسٹور کو شارڈ (shard) نہ کریں یا پیچیدہ پارٹیشننگ اسکیمیں متعارف نہ کروائیں جب تک کہ آپ کی مانیٹرنگ یہ ثابت نہ کر دے کہ آپ نے سادہ حل آزما لیے ہیں۔ ان فیلڈز پر انڈیکس لگائیں جنہیں آپ کوئری کرتے ہیں۔ اپینڈ-اونلی ورک لوڈز کے لیے autovacuum کو ٹیون کریں۔ پھر دوبارہ پیمائش کریں۔
سب کچھ ٹیسٹ کریں۔ اپنے ایونٹ ہینڈلرز کا یونٹ ٹیسٹ کریں۔ اپینڈ (append) پاتھ کا انٹیگریشن ٹیسٹ کریں۔ سب سے اہم بات یہ ہے کہ ناکامی کے منظرناموں (failure scenarios) کا ٹیسٹ کریں۔ کیا ہوتا ہے جب دو نوڈز بیک وقت ایک ہی اسٹریم میں ڈیٹا اپینڈ کرتے ہیں؟ کیا ہوتا ہے جب ایک پروجیکشن ورکر بیچ (batch) کے دوران کریش ہو جائے؟ ایسے ٹیسٹ لکھیں جو آپ کی آپٹیمسٹک کنکرنسی (optimistic concurrency) اور ایٹ لیسٹ ونسی ڈیلیوری (at-least-once delivery) کی ضمانتوں کی تصدیق کریں۔
پروڈکشن میں مانیٹر کریں۔ ایونٹس ٹیبل کا سائز بڑھتا جائے گا۔ ایک نارملائزڈ اسکیمہ کے برعکس جہاں اپ ڈیٹس سے روز کاؤنٹ (row count) مستحکم رہتا ہے، ایونٹ سورسنگ جان بوجھ کر اضافہ کرنے والی (additive) ہوتی ہے۔ ٹیبل کے سائز، ڈسک I/O، اور اپنے رائٹ ماڈل اور ریڈ ماڈل پروجیکشنز کے درمیان وقفے (lag) پر نظر رکھیں۔ صارفین کے پرانا ڈیٹا (stale data) کو محسوس کرنے سے پہلے پروجیکشن لیگ (projection lag) پر الرٹس سیٹ کریں۔
دستی کاموں کو خودکار بنائیں۔ دستی اسکیمہ تبدیلیاں، دستی پروجیکشن ری بلڈز، اور دستی ایونٹ ری پلے ٹِکنگ ٹائم بم (ticking time bombs) کی طرح ہیں۔ اپنی مائیگریشن حکمت عملی کو اسکرپٹ کریں۔ اگر آپ ایونٹ اسکیمہ کو تبدیل کرتے ہیں، تو اپ کاسٹنگ (upcasting) یا تبدیلی کو خودکار بنائیں تاکہ پرانے ایونٹس کو آدھی رات کو انسانی مداخلت کے بغیر نئی لاجک کے ذریعے ری پلے کیا جا سکے۔
اپنے فیصلوں کی دستاویز سازی کریں۔ لکھیں کہ مخصوص اسٹریمز کیوں موجود ہیں، ہر ایونٹ ٹائپ کا کیا مطلب ہے، اور ٹیم کو ایونٹس کے بجائے CRUD کب منتخب کرنا چاہیے۔ ایونٹ سورسنگ سے ذہنی بوجھ (cognitive load) بڑھتا ہے۔ اچھی دستاویز سازی ایک نئے انجینئر کو غلط اندازہ لگانے اور کسی اہم اسٹریم میں غلط فارمیٹ والے ایونٹس اپینڈ کرنے سے روکتی ہے۔
وہ جال جو مہینوں ضائع کر دیتے ہیں
ایونٹ سورسنگ ڈائیگرام میں تو پروقار لگتی ہے لیکن پروڈکشن میں تکلیف دہ ثابت ہو سکتی ہے۔ ان جالوں سے ہوشیار رہیں۔
پیچیدگی کو کم سمجھنا۔ اسٹیٹ (state) کو دوبارہ بنانے کے لیے ایونٹس کو ری پلے کرنا تصوراتی طور پر سادہ ہے۔ لیکن آئیڈیم پوٹینسی (idempotency) کو سنبھالنا، کارکردگی کے لیے اسنیپ شاٹنگ (snapshotting)، اور ایگریگیٹس کے درمیان معاوضہ دینے والے ٹرانزیکشنز (compensating transactions) کو مینیج کرنا آسان نہیں ہے۔ اپنے سسٹم کو چھوٹے حصوں میں تقسیم کریں۔ ایک وقت میں ایک اسٹریم کا حل نکالیں۔
اوور انجینئرنگ۔ صرف اس لیے ملٹی نوڈ Kafka کلسٹر تیار نہ کریں کہ آپ کو لگتا ہے کہ ایک دن آپ کے ایونٹ کا حجم اس کی ضرورت محسوس کرے گا۔ Postgres آپ کو حیرت انگیز طور پر کافی دور تک لے جا سکتا ہے۔ نیا انفراسٹرکچر صرف اس وقت متعارف کروائیں جب آپ کے پاس کوئی پیمائش شدہ رکاوٹ (bottleneck) ہو جسے آپ اپنے موجودہ سیٹ اپ میں ٹھیک نہ کر سکیں۔
تکنیکی قرض (technical debt) کو نظر انداز کرنا۔ پرانے ایونٹ اسکیمہ ہمیشہ رہتے ہیں۔ اگر آپ اپنے OrderCreated پے لوڈ کو تبدیل کرتے ہیں، تو آپ کے پاس اب بھی پرانے فارمیٹ میں دس ملین تاریخی ایونٹس موجود ہوں گے۔ اس قرض پر نظر رکھیں۔ بیک ورڈ-کمپیٹیبل (backward-compatible) ریڈرز یا مائیگریشن اسکرپٹس کا منصوبہ بنائیں۔ پرانے ایونٹس کے بوجھ کو ہر نئے فیچر کی رفتار کم نہ کرنے دیں۔
ایسے ٹولز کا انتخاب کرنا جنہیں ٹیم چلا نہ سکے۔ بہترین آرکیٹیکچر بھی ناکام ہو جاتا ہے اگر اسے صرف ایک شخص سمجھتا ہو۔ اگر آپ کی ٹیم Postgres اور SQL جانتی ہے، تو وہیں سے شروع کریں۔ اگر آپ کوئی مخصوص ایونٹ اسٹور متعارف کرواتے ہیں، تو یقینی بنائیں کہ آپ کے پاس رات کے دو بجے اسے ڈی بگ (debug) کرنے کی آپریشنل مہارت موجود ہو۔
ایک عملی آغاز
اگر یہ طریقہ آپ کے مسئلے کے لیے موزوں ہے، تو پانچ کوارٹر کے ری رائٹ (rewrite) کا انتظار نہ کریں۔ اسی ہفتے سے شروع کریں۔
اپنے موجودہ سسٹمز کا آڈٹ کریں۔ ایسی جگہ تلاش کریں جہاں آڈٹ ٹریل (audit trail) حقیقی مشکل کو حل کر سکے۔ ہو سکتا ہے کہ یہ کوئی آرڈر اسٹیٹ مشین ہو جو فی الحال صرف ایک status کالم برقرار رکھتی ہے۔ شاید یہ کوئی مالیاتی لیجر (financial ledger) ہو جہاں بیلنس کی درستگی کے لیے دستی ڈیٹا بیس پیچز کی ضرورت پڑتی ہے۔ کوئی ایسا خلا منتخب کریں جہاں اسٹیٹ کو اوور رائٹ کرنے سے آپ کو نقصان پہنچا ہو۔
پھر ایک چھوٹا سا بہتری کا قدم اٹھائیں جو آپ آج کر سکتے ہیں۔ ایک ایونٹ ٹیبل بنائیں۔ ایک اسٹریم ماڈل کریں۔ ایک ایسی پروجیکشن لکھیں جو ان ایونٹس سے ریڈ ماڈل تیار کرے۔ اسے فیچر فلیگ (feature flag) کے پیچھے ڈیپلائے کریں۔ اسے حقیقی ٹریفک کو سنبھالتے ہوئے دیکھیں۔
PostgreSQL کے ساتھ ایونٹ سورسنگ کوئی جادو نہیں ہے۔ یہ ان ٹیموں کے لیے ایک عملی ٹول ہے جنہیں نہ صرف یہ جاننے کی ضرورت ہے کہ چیزیں کہاں ہیں، بلکہ یہ بھی کہ وہ وہاں کیسے پہنچیں۔ آہستہ آہستہ تعمیر کریں، ایمانداری سے پیمائش کریں، اور اپنی اصل ضروریات کو آرکیٹیکچر کی رہنمائی کرنے دیں۔
