ইভেন্ট-ড্রিভেন আর্কিটেকচার
এই পাঠে যা শিখবেন
- ইভেন্ট-ড্রিভেন আর্কিটেকচার কী এবং এটি ডাইরেক্ট মেথড-কল থেকে কীভাবে ভিন্ন
- producer, consumer ও broker/bus — এই তিনটি উপাদান কীভাবে একসাথে কাজ করে
- এটি কীভাবে Observer প্যাটার্নের একটি আর্কিটেকচার-স্কেল, আরও লুজলি-কাপলড সংস্করণ
- একটি বাস্তব Python
EventBusবানিয়ে দেখানো, নতুন consumer যোগ করলেও producer অপরিবর্তিত থাকে
১ · ইভেন্ট-ড্রিভেন আর্কিটেকচার কী
ইভেন্ট-ড্রিভেন আর্কিটেকচারEvent-Driven Architectureএকটি আর্কিটেকচারাল স্টাইল যেখানে কম্পোনেন্টগুলো একে অপরের মেথড সরাসরি কল না করে "ইভেন্ট" তৈরি ও গ্রহণ করে যোগাযোগ করে।
একটি আর্কিটেকচারাল স্টাইল, যেখানে সিস্টেমের কম্পোনেন্টগুলো একে অপরের মেথড সরাসরি কল করার বদলে
ইভেন্ট — অর্থাৎ "কিছু একটা ঘটেছে" এই তথ্যের একটি রেকর্ড, যেমন
"OrderPlaced" বা "PaymentReceived" — তৈরি ও গ্রহণ করে যোগাযোগ করে। M6/L25-এর
লেয়ার্ড আর্কিটেকচারে যেমন প্রতিটি লেয়ার নিচের লেয়ারকে সরাসরি চেনে ও কল করে, এখানে ঠিক তার উল্টো — একটি
কম্পোনেন্ট শুধু ঘোষণা করে "এই ঘটনাটি ঘটেছে," আর কে সেটি নিয়ে কী করবে তা সেই কম্পোনেন্টের জানারই দরকার নেই।
২ · মূল উপাদান — Producer, Consumer, Broker/Bus
তিনটি অংশ নিয়ে এই আর্কিটেকচার গঠিত (প্রতিটি নির্দিষ্ট করে বলা প্রয়োজন):
কিছু ঘটেছে শনাক্ত করে একটি ইভেন্ট emit করে — কে (যদি কেউ থাকে) সেটি consume করবে, তা জানারও দরকার নেই।
নির্দিষ্ট ইভেন্ট টাইপে subscribe করে রাখে, এবং সেই ইভেন্ট ঘটলে সাড়া দেয়।
মাঝখানে বসে ইভেন্ট রাউট করে — producer আর consumer একে অপরকে সরাসরি চেনেই না, শুধু broker-কে চেনে। বাস্তব সিস্টেমে এটি প্রায়ই একটি "message queue" প্রযুক্তি হিসেবে বাস্তবায়িত হয় — এর আসল অবকাঠামো ও টুলিং Cloud Computing & DevOps কোর্সে।
৩ · এটি আসলে Observer প্যাটার্নের আর্কিটেকচার-স্কেল সংস্করণ
Observer প্যাটার্নObserver Patternএকটি সাবজেক্টের state বদলালে তার রেজিস্টার্ড observer-দের স্বয়ংক্রিয়ভাবে জানানো — M5/L23-এ দেখা একটি বিহেভিয়ারাল ডিজাইন প্যাটার্ন।
(M5/L23) মনে আছে? ইভেন্ট-ড্রিভেন আর্কিটেকচার আসলে সেই একই ধারণা — কিন্তু এখন একটি ক্লাসের
বদলে পুরো একটি সিস্টেম/সার্ভিসের স্কেলে। তবে একটি গুরুত্বপূর্ণ পার্থক্য আছে: Observer
প্যাটার্নে subject নিজে সরাসরি observer-দের একটি লিস্ট রেফারেন্স হিসেবে ধরে রাখে (subscribe()
কল করলে subject-এর নিজের লিস্টে যোগ হয়)। ইভেন্ট-ড্রিভেন আর্কিটেকচারে producer consumer-দের কোনো লিস্টই
রাখে না — সে শুধু broker-কে ইভেন্ট পাঠায়, broker রাউটিং সামলায়। ফলে কাপলিং আরও কম — producer আক্ষরিক
অর্থে জানেই না যে কোনো consumer আদৌ আছে কিনা।
৪ · সুবিধা — স্বাধীন স্কেলিং ও শূন্য-পরিবর্তনে সম্প্রসারণ
M6/L26-এ মাইক্রোসার্ভিসের একটি সুবিধা ছিল স্বাধীন স্কেলিং — ইভেন্ট-ড্রিভেন আর্কিটেকচার এই সুবিধাকে আরও এগিয়ে নেয়: producer আর consumer একে অপরকে সরাসরি রেফারেন্স করে না বলে তাদের আলাদাভাবে ডেভেলপ, ডিপ্লয় ও স্কেল করা যায়। আর সবচেয়ে গুরুত্বপূর্ণ প্র্যাকটিক্যাল সুবিধা — একটি বিদ্যমান ইভেন্ট টাইপের জন্য একটি নতুন consumer যোগ করতে producer-এর কোডে শূন্য পরিবর্তন লাগে — এটি L16-এর Open/Closed PrincipleOCPসফটওয়্যার এনটিটি এক্সটেনশনের জন্য open, মডিফিকেশনের জন্য closed থাকা উচিত। -এর সরাসরি বাস্তবায়ন, কিন্তু এখন ক্লাস-লেভেলে নয়, পুরো আর্কিটেকচার-লেভেলে।
নিচের কোড সেলে একটি বাস্তব EventBus বানানো হয়েছে — এটি স্যান্ডবক্স-সিমুলেশন নয়, এটি ordinary,
সম্পূর্ণ কার্যকরী Python OOP, ঠিক Observer-এর মতোই।
class EventBus:
def __init__(self):
self.subscribers = {} # event_type -> handler function-দের লিস্ট
def subscribe(self, event_type, handler):
self.subscribers.setdefault(event_type, []).append(handler)
def publish(self, event_type, data):
for handler in self.subscribers.get(event_type, []):
handler(data)
class OrderService:
def __init__(self, bus):
self.bus = bus # OrderService শুধু bus-কে চেনে, কোনো consumer-কে সরাসরি চেনে না
def place_order(self, order_id, item):
print(f"OrderService: অর্ডার #{order_id} তৈরি হলো ({item})")
self.bus.publish("OrderPlaced", {"order_id": order_id, "item": item})
class EmailService:
def handle_order_placed(self, data):
print(f" EmailService: কনফার্মেশন ইমেইল পাঠানো হলো -- অর্ডার #{data['order_id']}")
class InventoryService:
def handle_order_placed(self, data):
print(f" InventoryService: স্টক থেকে '{data['item']}' বিয়োগ করা হলো")
class AnalyticsService:
def handle_order_placed(self, data):
print(f" AnalyticsService: অর্ডার #{data['order_id']} অ্যানালিটিক্সে লগ করা হলো")
bus = EventBus()
order_service = OrderService(bus)
email_service = EmailService()
inventory_service = InventoryService()
# ধাপ ১: দুইজন consumer সাবস্ক্রাইব করছে
bus.subscribe("OrderPlaced", email_service.handle_order_placed)
bus.subscribe("OrderPlaced", inventory_service.handle_order_placed)
print("== প্রথম অর্ডার (২ জন consumer সাবস্ক্রাইবড) ==")
order_service.place_order(101, "ল্যাপটপ")
# ধাপ ২: তৃতীয় consumer যোগ -- OrderService-এর কোডে কোনো পরিবর্তন লাগেনি
analytics_service = AnalyticsService()
bus.subscribe("OrderPlaced", analytics_service.handle_order_placed)
print()
print("== দ্বিতীয় অর্ডার (৩ জন consumer সাবস্ক্রাইবড, OrderService অপরিবর্তিত) ==")
order_service.place_order(102, "মাউস")
OrderService.place_order() মেথডে EmailService,
InventoryService, বা AnalyticsService — কারও নামই কোথাও লেখা নেই। এটিই এই
আর্কিটেকচারের মূল কথা — producer শুধু self.bus.publish(...) কল করে, তারপর সম্পূর্ণ ভুলে যায়
কে সেই ইভেন্ট নিয়ে কী করছে।
ইভেন্ট-ড্রিভেন আর্কিটেকচার producer আর consumer-কে একটি broker/bus দিয়ে আলাদা করে দেয় — ফলে তারা একে অপরকে সরাসরি না চিনেই যোগাযোগ করতে পারে। এটি M5/L23-এর Observer প্যাটার্নের একটি আরও লুজলি-কাপলড, আর্কিটেকচার-স্কেল সংস্করণ, এবং L16-এর OCP-কে বাস্তবে প্রমাণ করে — একটি বিদ্যমান ইভেন্টের জন্য নতুন consumer যোগ করা মানেই producer-এর একটি লাইনও বদলানো নয়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ Observer প্যাটার্ন (M5/L23) আর ইভেন্ট-ড্রিভেন আর্কিটেকচারের মধ্যে ঠিক কোন কাপলিং-এর পার্থক্যটি সবচেয়ে গুরুত্বপূর্ণ?
Observer-এ subject নিজে সরাসরি তার observer-দের একটি লিস্ট রেফারেন্স হিসেবে ধরে রাখে — subject জানে ঠিক কারা তাকে observe করছে। ইভেন্ট-ড্রিভেন আর্কিটেকচারে producer কোনো consumer-এর লিস্টই রাখে না — সে শুধু broker-কে ইভেন্ট পাঠায়, broker রাউটিং সামলায়। ফলে producer আক্ষরিক অর্থে জানেই না কোনো consumer আদৌ আছে কিনা, এমনকি কতজন আছে তাও না — এটি Observer-এর চেয়েও একধাপ কম কাপলিং।
প্র ০২
উপরের কোড সেলে যদি AnalyticsService যোগ করার বদলে EmailService-কে সরিয়ে দেওয়া হতো, তাহলে OrderService-এর কোডে কি কোনো পরিবর্তন লাগত?
না। শুধু bus.unsubscribe(...)-জাতীয় একটি কল (বা সাবস্ক্রাইব লিস্ট থেকে সরানো) দরকার হতো —
OrderService.place_order() মেথডে কোনো পরিবর্তনই লাগত না, কারণ সে কখনোই
EmailService-কে সরাসরি চেনেনি। এটিই decoupling-এর আসল প্র্যাকটিক্যাল সুবিধা — consumer
যোগ করা বা সরানো, দুটোই producer-এর জন্য সম্পূর্ণ অদৃশ্য।
প্র ০৩ ইভেন্ট-ড্রিভেন আর্কিটেকচারের কোনো সততার সাথে বলা weakness/trade-off কী হতে পারে, শুধু সুবিধা নয়?
যেহেতু producer জানেই না কারা consume করছে, তাই একটি ইভেন্ট প্রসেস হওয়ার পুরো ফ্লো ট্রেস করা কঠিন হতে পারে — একটি বাগ ডিবাগ করতে গেলে বুঝতে হয় ঠিক কতজন consumer সেই ইভেন্টে সাবস্ক্রাইবড এবং প্রতিটি কী করছে, যা M6/L25-এর লেয়ার্ড আর্কিটেকচারের সরাসরি, অনুমানযোগ্য কল-চেইনের তুলনায় কম স্বচ্ছ। এটি genuinely একটি অপারেশনাল জটিলতা, শুধু একটি নিখুঁত সমাধান হিসেবে দেখা উচিত নয়।
অনুশীলন
-
চিন্তা করুন: একটি ই-কমার্স সিস্টেমে "PaymentReceived" নামে একটি নতুন ইভেন্ট টাইপ চালু করা হলে, কোন কোন consumer সেই ইভেন্টে আগ্রহী হতে পারে তার একটি তালিকা করুন (উদাহরণ: একটি রসিদ পাঠানো, একটি শিপিং প্রসেস শুরু করা)।
সম্ভাব্য consumer: একটি
ReceiptService(গ্রাহককে রসিদ ইমেইল করে), একটিShippingService(শিপমেন্ট প্রসেস শুরু করে), একটিFraudDetectionService(সন্দেহজনক পেমেন্ট প্যাটার্ন পরীক্ষা করে), এবং একটিAccountingService(রাজস্ব রেকর্ড আপডেট করে) — এই প্রতিটিই স্বাধীনভাবে "PaymentReceived" ইভেন্টে সাবস্ক্রাইব করতে পারে, কোনোটিই পেমেন্ট প্রসেস করা কম্পোনেন্টের কোডকে সরাসরি স্পর্শ না করেই। -
পরীক্ষা করুন: উপরের কোড সেলে একটি নতুন
ShippingServiceক্লাস (নিজের একটিhandle_order_placedমেথডসহ) যোগ করে সেটিকে বাসে সাবস্ক্রাইব করে দেখুন —order_service.place_order(...)কল একই রেখে আউটপুটে চতুর্থ consumer-ও সাড়া দিচ্ছে কিনা যাচাই করুন।নতুন
ShippingServiceক্লাস যোগ করেbus.subscribe("OrderPlaced", shipping_service.handle_order_placed)কল করলেই যথেষ্ট — পরবর্তীorder_service.place_order(...)কলের আউটপুটে এখন চারজন consumer-ই সাড়া দেবে, এবংOrderServiceক্লাসের একটি লাইনও বদলাতে হবে না — কোড সেলের মূল দাবিটির সরাসরি প্রমাণ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ · MVC ও MVVM প্যাটার্ন L28 M6-এর শেষ পাঠ — Observer প্যাটার্ন আবারও ফিরে আসছে, এবার একটি সম্পূর্ণ MVC ট্রায়োর ভেতরে।
- বিহেভিয়ারাল প্যাটার্ন — Observer ও Strategy L23 এই পাঠের ভিত্তি — Observer প্যাটার্নের ক্লাস-লেভেল সংস্করণটি একবার ঝালিয়ে নিন।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M7-এ Git ফাউন্ডেশন শুরু হচ্ছে — থ্রি-ট্রি আর্কিটেকচার, status/diff/log ও আরও অনেক কিছু।