DEV Community

Monirul Islam
Monirul Islam

Posted on Originally published at mislam-dev.vercel.app

Day 18 — Event-Driven Architecture — Microservice-এর প্রাণ

মনে করুন আপনি একটা মোবাইল ফোন কিনেছেন lower-mid budget। Budget low হওয়াতে সব কিছুই low configuration এবং minimum দেয়া আছে। এখন আপনার competitive game (PUBG mobile, Call of Duty) খেলতে ইচ্ছা জাগলো। এই গেমগুলো খেলার minimum requirements আপনার ফোনে নেই। Processor, RAM কোনোটাই আশেপাশে নেই। এখন আপনি কি চাইলেই processor বা RAM change করতে পারবেন? Same question-টা যদি camera-এর জন্য বলি? সেক্ষেত্রেও না।

Same জিনিস এবার একটা desktop computer-এর দিকে দেখেন, এখানে সব কিছুই আছে। Mobile phone-এর মতো সব partsগুলো মিলে একটা system হিসেবে কাজ করে। কিন্তু আপনি চাইলেই এই partsগুলো individually change করতে পারবেন।

এখানে দুইটাতেই similar type of parts use করার পরেও একটাতে change করা যাচ্ছে আরেকটাতে যাচ্ছে না। এই চমৎকারটা depend করে এর design-এর উপরে। Desktop computer-টা modular way-তে design করা হয়ে থাকে। এই জন্য easily change করা যায়। মোটামুটি সব কিছুই portable-এর মতো। কিন্তু mobile phone সব tightly integrated হয়ে থাকে।

Desktop computer-এর Modular Architectureগুলো loosely coupled থাকে। অনেক portable-এর মতো। Easily যেকোনো system add করা যায় আবার remove করা যায়।

Modular Architecture কী?

Modular Architecture-এর concept বুঝে গেছেন। Codebase feature বা domain অনুযায়ী একটা module-এ ভাগ করে ফেলা হয়। এই module-এর ভেতরে শুধু ওই feature related সব কিছু থাকবে। এবং একটা entry point থাকবে। আর ওই entry point দিয়ে main application-এর সাথে connect হবে। যখন দরকার হবে না তখন ওই entry point disconnect করে দিলেই, module disconnect হয়ে যাবে।

Modular architecture choose করার main reason হচ্ছে, future-এ যদি traffic বেড়ে যায় তাহলে এটা যাতে easily microservice-এ transform করা যায়।

এখন কথা হচ্ছে, যখন communicate করার জন্য অন্য কোনো service দরকার হয় তখন ঝামেলা বেধে যায়। এখন যদি directly কোনো module-এর উপরে depend হয়ে যায় তাহলে module ওই module-এর সাথে tightly coupled হয়ে যাচ্ছে। এই type module microservice-এ transform করা একটু কষ্টসাধ্য বিষয়, অনেক ঝামেলা পোহাতে হবে। এটার best solution হচ্ছে Events

এখানে HTTP-এর কথা মাথায় আসতে পারে। তবে এই moduleগুলো একই codebase-এর ভেতরেই আছে so HTTP এখানে useless।

Event-Driven Architecture

Event-Driven Architecture-এ events হলো main জিনিস। এখানে events-এর ওপরেই base করে সব decision নেওয়া হয়।

একটা service একটা event produce করবে, তারপর ওই event-টা যেই যেই service-এর দরকার তারা consume বা receive করবে। এরপর event-এর ওপর নির্ভর করে বাকি কাজগুলো করবে। That's it।

আগের আর্টিকেলে একটা order নিয়ে কথা বলেছিলাম যেখানে order করার পরে কিছু background task করার দরকার হয়। ওই system-টাতেও চাইলে event use করা পসিবল। Process-টি হবে নিচের মতো:

  • User order করলো, তখন আমরা একটি event produce করব এবং user-কে response সেন্ড করে দেব।
  • অন্যান্য service যেমন- email, message, inventory এবং Analytics service এই event-টা consume করবে। তারপর তারা তাদের কাজ individually করবে।

By the way, সব ধরনের application-এ event-driven architecture use করা যায়।

Event-Driven Architecture সব থেকে বেশি use হয় Microservice & Distributed system-এ। সামনে microservice নিয়ে dedicated একটা article আসবে এই জন্য এখানে বেশি কিছু বলছি না। তবে এই article-এ distributed system related কিছু concept আলোচনা করতে হবে।

কেন Events?

মনে করুন, Order service সরাসরি Email service-কে call করছে।

এখন এই ৪টি task:

  • send email
  • send sms
  • analytics update
  • inventory update

এখন এই কাজগুলো same time-এ করলে অনেক সময় লাগবে। এছাড়াও আগের আর্টিকেলে কী কী problem হতে পারে তা নিয়ে আলোচনা করেছিলাম।

Suppose, কোনো কারণে email service down হয়ে গেছে। তাহলে তো order fail হবে। এতে user experience খারাপ হবে। Event-driven architecture-এ যখনই email service up হবে, তখনই মেইলটি send করে দেবে। এক্ষেত্রে হয়তো delay হবে, কিন্তু order cancel হবে না। অন্যান্য service-এর ক্ষেত্রেও same rule apply হবে। User-কে email এবং sms processing-এর জন্য wait করতে হবে না।

Eventual Consistency

Monolith application (without EDA)-এ সবকিছু synchronous হয়, প্রথমে processing এবং update হয়, তারপর response return করে।

EDA-তে সবকিছুই background processing-এর মতো কাজ করে। তাই immediately data consistent হবে না। একটু পরে consistent হবে। সুতরাং, এটি eventually consistent হবে। মনে করুন একটা Order placed হলো দুপুর ১২:০০টায়। Email send হলো ১২:০০:০৩-এ। এই ৩ সেকেন্ড - data সব জায়গায় consistent না। কিন্তু eventually consistent হবে।

এই mental model-টা accept না করলে Event-driven architecture কঠিন হবে।

Pub/Sub Pattern: সবচেয়ে Popular Pattern

একটা জিনিস, আপনি যখন YouTube ভিডিও দেখেন তখন আপনি আপনার পছন্দের Creator-এর YouTube Channel Subscribe করেন। কেন করেন? যাতে আপনি তার ভিডিও আপলোড হলেই একটা Notification পান। এই তো। এখানে আপনি তো আর একা নন, আপনার মতো আরও অনেক মানুষ আছে। যখন ওই Creator কোনো ভিডিও Publish করবে ঠিক তখনই সকল Subscriber-এর কাছে একটা Notification চলে যাবে।

Pub/Sub Pattern-টা similar। যখন কোনো Event Publish হবে, তখন ওই Event-এ যে যে Service Subscribe করে রেখেছে, সেই Serviceগুলোকে Notify করা হবে—যে এই Event-টা Publish হয়েছে, তুমি তোমার কাজ এবার শুরু করতে পারো।

একটা Order Place হওয়ার পর কী কী করতে হবে?

  1. Inventory update করতে হবে
  2. Mail + SMS send করতে হবে
  3. Analytics update করতে হবে

মনে করুন কোনো Order Place হলে, "OrderPlaced" নামে একটা Event Publish হবে। এখন এই ৩টা Service-ই Event-কে Subscribe করে থাকবে।

এখন একটা Order Place হলো। "OrderPlaced" নামে একটা Event তো Publish হলো। এই Event Publish হওয়ার সাথে সাথে ৩টা Service-এ Notification চলে যাবে। তখন ওই Serviceগুলো independently তাদের কাজ করবে।

এখানে একটা জিনিস খেয়াল করুন, Just একটা Event Publish হয়েছে, এতে কত কিছু হয়ে গেল! আপনি চাইলে এই Event দিয়ে অন্য কাজও করতে পারেন। চাইলে Logging Service-ও এটা Consume করতে পারে।

এখানে মজার বিষয় কী জানেন, Order Service জানেই না যে এই Event কে বা কারা Consume করবে।


Delivery Semantics

Event-Driven Architecture asynchronous। এই জন্য Event Delivery করার সময় Delivery Mode জেনে রাখা খুবই জরুরি। এই জিনিসগুলো পরবর্তীতে একটা Better System + Debugging-এ Help করবে। এখানে ৩টা Mode আছে:

  • At-most-once: এই Mode-এ Event-কে একবারই পাঠানো হয়, সেটা পৌঁছাল কিনা তা জানার কোনো উপায় নেই। Fire and forget। Critical কাজের জন্য এটা Avoid করা better।

  • At-least-once: Word দেখেই বুঝে গেছেন, এই Mode-এ Event অন্তত একবার হলেও পৌঁছাবে। তবে এখানে Event Duplication-এর Chance থাকে। এই জন্য Consumer-কে Idempotent হতে হবে। এক্ষেত্রে Duplicate Events আসলেও Result একই থাকবে।

  • Exactly-once: এই Mode-এ Event ১ বারই আসবে এবং Exactly ১ বারই Process হবে। কিন্তু বাকিগুলোর তুলনায় এটা Difficult, Complex এবং Expensive। এটা Use করলে এখানে একটা Performance Cost দিতে হয়।

বেশিরভাগ ক্ষেত্রে at-least-once + idempotent consumer use করা best option।


Dead Letter Queue (DLQ): Failure-র জন্য Plan B

নানা ধরনের কারণে, Events Processing নাও হতে পারে, যেমন Network Failure, Database Connection Failure, Invalid Event Data, Consumer Code-এ Error ইত্যাদি। এই জন্য Retry-এর ব্যবস্থা করা হয়।

এর পরেও অনেক Events থাকবে যেগুলো Process-ই হবে না। তখন কী হবে?? এই ধরনের Eventগুলোকে বলে Dead Letter। এই টাইপের Event-এর জন্য আসে Dead Letter Queue (DLQ)
এই Eventsগুলো বারবার Fail হওয়ার পর DLQ-তে চলে যাবে। পরবর্তীতে এগুলো Manually Inspect করা হয়। এখান থেকে চাইলে এগুলো Retry-ও করা যায়।

Event-Driven Architecture ব্যবহার করলে এই DLQ না থাকলে আপনার Application-এর Reliability অনেক কমে যাবে। Debug করাও একটু কঠিন হয়ে যাবে।


Outbox Pattern: Database আর Event-এর মেলবন্ধন

মনে করুন একটা Service, একটা Event Consume করলো, Consume করার সাথেই Service-টা Crash করলো। তখন কী হবে? তখন কিন্তু ওই Event-এর Opposite-এ যে Actionগুলো Define করা ছিল সেগুলো কিন্তু Execute হবে না। Inconsistency দেখা দিবে।
Outbox Pattern এটার Solution Provide করে। আগে Event-কে Database-এ Store করতে হবে। Then Processing হবে, সকল Actionগুলো Execute হবে, Then Database থেকে ওই Event-টা Remove করে দিতে হবে। এটা Application-এর Consistency ঠিক রাখতে Help করে।

এই টাইপের জিনিসগুলো Manually করা একটু Painful + Long-term Debugging করা Impossible হয়ে যেতে পারে। এই জন্য Existing Message Broker যেমন RabbitMQ, Kafka, NATS ইত্যাদি Use করা Better, এগুলোতে এই Outbox Pattern In-built থাকে। মূলত এই ধরনের Taskগুলো Properly + Efficiently Handle করার জন্যই বানানো হয়েছে।


Observability: না দেখলে জানবেন কীভাবে?

Asynchronous system-এ almost সব কাজ Background-এ Process হয়। এই কারণে Synchronous-এর থেকে এখানে Debugging করা একটু Difficult।

  • Correlation ID — এখানে প্রতিটা Event-এর একটা Unique ID থাকবে। এই একটা ID দিয়ে সম্পূর্ণ System Trace করা Possible হবে। এই ID দিয়েই Detect করা যাবে যে, কোথায় কোথায় এই Event Consume করা হয়েছে।
  • Consumer Lag — অনেক সময় Consumer-এর Processing Power কম হওয়াতে Event properly Process করতে পারে না। সেক্ষেত্রে Broker-এর Event + Consumer-এর Difference-টা Detect করতে হবে। Then প্রয়োজন হলে Consumer-কে Scale Up করতে হবে।

সব থেকে Easy হয় Existing Observability Tool Use করা। For example: Prometheus, Grafana, Loki, Sentry। আমি Personally Backend-এর Tracing-এর জন্য Sentry Use করি।


বটম লাইন

পুরানো কথা আবারও বলছি, একটা system-কে আপনি যত বেশি split করবেন ততই complexity বাড়বে। অনেকটা চোরাবালির মতো।

Event-Driven Architecture একটা system-এর modularity বাড়াতে help করে। সাথে সাথে এর fault tolerance + resilience বাড়ে। সাথে extensibility-ও বাড়াতে help করে। আপনি easily EDA-এর মাধ্যমে একটা নতুন module add করতে পারবেন। EDA best হচ্ছে big + complex application-এর জন্য। ছোট application-এর জন্য EDA add করে complexity বাড়ানো উচিত না।

Event-Driven Architecture নিয়ে আপনার কী experience আছে অবশ্যই share করবেন।

Top comments (0)