Outbox ও Inbox: database ও event-এর সমন্বয়
Outbox, Inbox, and Change Data Capture
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Database ও broker dual-write gap ধরুন
- Outbox relay ও inbox মিলান
- CDC-এর ভূমিকা এবং সীমা বুঝুন
সমস্যা দিয়ে শুরু
Order database-এ commit হলো, তারপর publish করার আগেই Laravel process বন্ধ। Warehouse কখনও order জানল না। Publish আগে করলে উল্টো সমস্যা: rollback হওয়া order-এর event বেরিয়ে যেতে পারে। দুটি স্বাধীন system-এ পরপর write atomic হয় না।
সহজ করে বুঝি
Transactional outbox business row ও event row একই database transaction-এ লেখে। Relay পরে outbox পড়ে broker-এ publish করে। Publish-এর পরে sent marker-এর আগে crash হলে duplicate আসতে পারে; তাই consumer inbox বা idempotent effect লাগে। CDC transaction log থেকে committed change stream করতে পারে।
পরিচিত একটি উপমা
দোকান order খাতার সঙ্গে dispatch খাতায় একই বসায় entry দেয়। Messenger পরে dispatch খাতা দেখে খবর পৌঁছায়। ফিরে tick দেওয়ার আগে messenger অসুস্থ হলে আবার খবর যেতে পারে; কিন্তু কোন order-এর খবর বাকি তা খাতায় থেকে যায়।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
flowchart LR A[Laravel Transaction] --> O[(Orders)] A --> X[(Outbox)] X --> R[Relay or CDC] R --> B[Broker] B --> I[Consumer Inbox] I --> D[(Consumer State)]
Orders ও outbox একই transaction boundary-তে। Relay কেবল committed row দেখে। Broker acceptance-এর পরে published marker লেখা হয়। Consumer marker ও effect নিজের transaction-এ রাখে। এই দুটি local atomic boundary dual-write gap ছোট করে না, স্পষ্টভাবে recoverable করে।
PHP ও Laravel-এ প্রয়োগ
Order service fragment; orders ও outbox_messages একই database connection-এ। outbox id unique এবং payload JSON column। Production relay-তে claim lease, batching, retry ও stuck record monitoring দরকার; afterCommit dispatch একা database ও broker-এর atomic transaction নয়।
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
$orderId = DB::transaction(function () use ($customerId, $amountMinor) {
$id = DB::table('orders')->insertGetId([
'customer_id' => $customerId,
'total_minor' => $amountMinor,
'status' => 'created',
]);
DB::table('outbox_messages')->insert([
'id' => (string) Str::uuid(),
'type' => 'order.created.v1',
'payload' => json_encode(['order_id' => $id], JSON_THROW_ON_ERROR),
'created_at' => now(),
'published_at' => null,
]);
return $id;
});এটি PHP 8.3+ ও Laravel 12-এর শিক্ষামূলক fragment; সম্পূর্ণ application নয়। সংশ্লিষ্ট route-এ authentication ও resource authorization প্রয়োগ করতে হবে। নিচের অনুশীলনে failure ইচ্ছা করে তৈরি করুন; শুধু সফল response দেখে correctness ধরে নেবেন না।
সুবিধা, সীমাবদ্ধতা ও trade-off
Outbox গুরুত্বপূর্ণ event হারানো ঠেকাতে durable retry point দেয়। Database storage, relay ও duplicate consumer handling যোগ হয়। CDC polling load কমাতে পারে কিন্তু connector, log retention ও schema management লাগে। ছোট noncritical email-এর জন্য সরল afterCommit যথেষ্ট হতে পারে, loss contract মেনে।
সাধারণ ভুল
- Published marker আগে লিখে পরে broker-এ পাঠানো।
- Outbox relay exactly-once transport দেয় বলা।
- Business row ও outbox আলাদা database-এ transaction ছাড়া লেখা।
আরও একটু গভীরে
Outbox event এবং table-level CDC এক নয়। Raw row change ব্যবসার domain contract নাও হতে পারে। CDC দিয়ে outbox table capture করলে explicit event contract রাখা যায়। Relay ordering per aggregate দরকার হলে sequence ও claim strategy design করতে হবে।
Commit-এর পর app kill করুন; relay restart-এ event যাবে। Broker publish-এর পরে marker লেখার আগে relay kill করুন; duplicate যাবে কিন্তু consumer effect একবার হবে। Oldest unpublished age alert দিন, শুধু queue depth নয়।
মনে রাখুন
- Business data ও event এক local transaction।
- Relay duplicate করতে পারে।
- Consumer correctness আলাদা boundary।
নিজে চেষ্টা করুন
দুই relay worker-এর claim scheme লিখুন। Slow publish-এ transaction lock কতক্ষণ ধরবেন এবং lease expiry-তে duplicate কীভাবে সামলাবেন বলুন।
নিজের বোঝা যাচাই করুন
1. afterCommit একই জিনিস?
না। Rollback-এর event ঠেকায়, কিন্তু commit ও dispatch-এর মাঝের process crash gap পুরো মেটায় না।
2. Outbox exactly-once?
না। Publish ও marker-এর মাঝে crash duplicate তৈরি করতে পারে।
3. Inbox কী?
Consumer-side durable event identity; effect-এর সঙ্গে atomicভাবে লিখলে local duplicate effect ঠেকে।
4. CDC কি event schema?
নিজে নয়। Table changes থেকে domain contract আলাদা রাখা দরকার হতে পারে।
5. কোন alert?
Oldest unsent age ও relay failures; rows জমছে কি না time-based সংকেত বেশি কার্যকর।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।