CQRS (Command Query Responsibility Segregation)
- ●CQRS-এ লেখা (command) আর পড়া (query)-র জন্য আলাদা model ও প্রায়ই আলাদা database ব্যবহার করা হয়।
- ●Read side-কে denormalized, query-র জন্য অপ্টিমাইজড করা যায়, write side থাকে normalized ও নিয়ম-কঠোর।
- ●এটা read-heavy, জটিল domain-এ দারুণ, কিন্তু eventual consistency ও বাড়তি জটিলতার দাম দিতে হয়।
সমস্যাটা কী?
একটা বড় e-commerce-এর product page ভাবো। একই product table থেকে দুই ধরনের কাজ হচ্ছে। এক, লেখা: admin দাম বদলায়, stock আপডেট হয়, নতুন product যোগ হয় — এখানে কঠোর নিয়ম, validation, transaction দরকার। দুই, পড়া: লক্ষ লক্ষ ক্রেতা product দেখছে, search করছে, filter করছে, rating দেখছে — এখানে দরকার বিদ্যুৎ-গতির read।
এক টেবিলে দুটো একসাথে সামলানো কঠিন। Read-কে দ্রুত করতে denormalize করলে write জটিল হয়; write-কে পরিষ্কার রাখতে normalize করলে read-এ অনেক join লেগে slow হয়। আবার read-এর ভয়ংকর চাপ write-এর performance নষ্ট করে। দুটোর প্রয়োজন আলাদা, অথচ এক জায়গায় বাঁধা।
CQRS বলে — তাহলে দুটোকে আলাদা করে ফেলো।
মূল ধারণা
CQRS (Command Query Responsibility Segregation) এমন একটা প্যাটার্ন যেখানে state পরিবর্তনের কাজ (Command) আর state পড়ার কাজ (Query)-কে আলাদা model-এ ভাগ করা হয়। এক model শুধু লেখার জন্য, আরেকটা শুধু পড়ার জন্য।
সাধারণ CRUD-এ একই model দিয়ে create/read/update/delete — সব হয়। CQRS এই অনুমানটাই ভাঙে: লেখা আর পড়ার চাহিদা ভিন্ন, তাই তাদের আলাদা পথ দাও।
- Command: কিছু করার নির্দেশ, যেমন
PlaceOrder— এটা state বদলায়, কোনো data ফেরত দেয় না। - Query: কিছু জানতে চাওয়া, যেমন
GetOrderHistory— এটা data ফেরত দেয়, কিছু বদলায় না।
কীভাবে কাজ করে
দুটো আলাদা পথ
- Write side: Command আসে, business নিয়ম যাচাই হয়, normalized write database-এ পরিবর্তন লেখা হয়।
- Sync/event: এই পরিবর্তন একটা event আকারে read side-এ পাঠানো হয়।
- Read side: event পেয়ে denormalized Read Model আপডেট হয় — যেটা query-র জন্য আগেই গুছিয়ে রাখা।
- Query side: ক্রেতার read request সরাসরি এই দ্রুত read model থেকে উত্তর পায়, write database-কে স্পর্শ না করে।
Read ও Write model-এর পার্থক্য
| দিক | Write Model | Read Model |
|---|---|---|
| উদ্দেশ্য | সঠিকতা, নিয়ম প্রয়োগ | দ্রুত পড়া |
| গঠন | normalized | denormalized (আগে থেকে join করা) |
| অপ্টিমাইজেশন | consistency | query speed |
| উদাহরণ store | PostgreSQL | Elasticsearch / Redis / read replica |
| scale | কম দরকার | অনেক বেশি (read replica) |
Read side-কে একাধিক read replica বা বিশেষ store দিয়ে আলাদাভাবে scale করা যায়, write side-কে না ছুঁয়ে।
Event Sourcing-এর সাথে জোট
CQRS প্রায়ই Event Sourcing-এর সাথে জোড়া বাঁধে। Event Sourcing-এ তুমি শুধু বর্তমান অবস্থা নয়, প্রতিটি পরিবর্তনকে একটা event হিসেবে ক্রমানুসারে জমা রাখো (OrderPlaced, OrderPaid, OrderShipped)। এই event-stream থেকেই write side তার অবস্থা বানায়, আর একই stream পড়ে যত খুশি read model বানানো যায়। দুটো প্যাটার্ন একে অপরকে স্বাভাবিকভাবে পরিপূরক করে।
একটা বড় লাইব্রেরি ভাবো। বইয়ের আসল ভাণ্ডার (write side) কঠোরভাবে সাজানো — প্রতিটি বইয়ের একটা নির্দিষ্ট তাক, কড়া নিয়মে রাখা হয়, যাতে হিসাব ঠিক থাকে। কিন্তু পাঠক তো এত নিয়ম জানে না; তারা ব্যবহার করে catalog/index (read side) — যেখানে বইগুলো বিষয়, লেখক, জনপ্রিয়তা অনুযায়ী আগে থেকেই সাজানো, খুঁজে পাওয়া সহজ। নতুন বই এলে আসল ভাণ্ডারে যোগ হয়, তারপর কিছুক্ষণ পর catalog আপডেট হয় (eventual consistency)। দুটো আলাদা ব্যবস্থা, দুই কাজের জন্য অপ্টিমাইজড।
সুবিধা ও অসুবিধা
সুবিধা:
- আলাদা scaling: read-heavy সিস্টেমে শুধু read side বাড়ানো যায়।
- অপ্টিমাইজড read: denormalized view-তে join ছাড়াই বিদ্যুৎ-গতির query।
- পরিষ্কার write model: business নিয়ম read-এর চাপ ছাড়াই একাগ্রভাবে প্রয়োগ করা যায়।
- নমনীয়তা: এক write থেকে একাধিক বিশেষায়িত read model (search, analytics, dashboard)।
- Event Sourcing-এর সাথে শক্তিশালী audit trail।
অসুবিধা:
- জটিলতা: দুটো model, sync ব্যবস্থা — কোডবেইজ অনেক জটিল হয়।
- Eventual consistency: write-এর পরপরই read-এ পুরনো data দেখা যেতে পারে।
- বেশি infrastructure: আলাদা store, message bus — খরচ ও রক্ষণাবেক্ষণ বাড়ে।
- শেখার বাঁক খাড়া: টিমের অভ্যস্ত হতে সময় লাগে।
কখন ব্যবহার করবে / করবে না
ব্যবহার করবে যখন: domain জটিল ও business নিয়ম ভারী, read ও write-এর load বা গঠন খুব আলাদা, read অনেক বেশি, কিংবা একই data-র অনেক রকম view দরকার।
এড়িয়ে যাবে যখন: সাধারণ CRUD অ্যাপ, ছোট সিস্টেম, কিংবা team-এর distributed system সামলানোর অভিজ্ঞতা কম।
সবচেয়ে বড় ভুল: পুরো সিস্টেমে অন্ধভাবে CQRS চাপিয়ে দেওয়া। CQRS পুরো অ্যাপের নিয়ম নয় — এটা নির্দিষ্ট bounded context-এ প্রয়োগ করার জিনিস, যেখানে read/write-এর চাহিদা সত্যিই আলাদা। সরল অংশে CQRS আনলে তুমি অকারণে eventual consistency-র বাগ আর তিনগুণ কোড পাবে, কোনো লাভ ছাড়াই। "যেখানে দরকার, শুধু সেখানে" — এটাই নিয়ম।
বাস্তব উদাহরণ
বড় e-commerce ও banking সিস্টেমে CQRS খুব প্রচলিত। ধরো একটা stock-trading প্ল্যাটফর্ম। লেনদেন (write) চরম নির্ভুল ও নিয়ম-কঠোর হতে হবে — প্রতিটি buy/sell command কঠোর validation পেরিয়ে event আকারে জমা হয়। অন্যদিকে লক্ষ লক্ষ ব্যবহারকারী একসাথে portfolio, price chart, history দেখছে — এই read গুলো denormalized read model (যেমন Elasticsearch) থেকে আসে, যাতে trading engine-এর উপর কোনো চাপ না পড়ে।
Microsoft তাদের কিছু Azure সেবা ও eShopOnContainers-এর মতো রেফারেন্স আর্কিটেকচারে CQRS + Event Sourcing দেখিয়েছে, যেখানে order-এর write side event-sourced আর read side আলাদা query-অপ্টিমাইজড database থেকে কাজ করে।
ইন্টারভিউতে CQRS বললে সাথে সাথে যোগ করো: "তবে এটা সবখানে নয় — শুধু সেই bounded context-এ যেখানে read/write আলাদা স্কেল বা গঠন চায়, আর প্রায়ই Event Sourcing-এর সাথে।" এই সংযম দেখালে তুমি প্রমাণ করো যে তুমি hype-এ গা ভাসাও না, trade-off বুঝে সিদ্ধান্ত নাও।
মূল শব্দ (Key Terms)
মিনি কুইজ
1. CQRS-এর মূল ধারণা কী?
2. CQRS-এ read model সাধারণত কেমন হয়?
3. CQRS-এর একটা বড় খরচ কী?