Observer Pattern vs Pub-Sub Pattern

সফটওয়্যার আর্কিটেকচার ডিজাইনে, বিশেষ করে ইভেন্ট-ড্রিভেন (Event-driven) সিস্টেম বানানোর সময় ডেভেলপাররা প্রায়ই Observer Pattern এবং Publisher-Subscriber (Pub-Sub) Pattern কে গুলিয়ে ফেলেন। কারণ বাইরে থেকে দেখতে এদের কাজ একই মনে হয়—একটি ঘটনা ঘটলে অন্য কাউকে জানানো।

কিন্তু সিস্টেম ডিজাইনের গভীরে গেলে এদের আর্কিটেকচার, স্কোপ এবং কাপলিংয়ের (Coupling) মধ্যে আকাশ-পাতাল তফাত রয়েছে। চলো এই দুটি প্যাটার্নের তুলনামূলক এবং গভীর বিশ্লেষণ করি।

Observer Pattern: ডিরেক্ট কমিউনিকেশন

Observer প্যাটার্নে মূলত দুটি পক্ষ থাকে: Subject এবং Observer।

এখানে Subject সরাসরি তার সাবস্ক্রাইবার বা অবসার্ভারদের চেনে। Subject নিজের মেমোরিতে একটি লিস্ট বা অ্যারে মেইনটেইন করে, যেখানে তার সব অবসার্ভারের রেফারেন্স রাখা থাকে। যখনই Subject-এর কোনো স্টেট পরিবর্তন হয়, সে লুপ চালিয়ে সরাসরি প্রতিটি অবসার্ভারের মেথড (যেমন: update()) কল করে দেয়।

বৈশিষ্ট্য:

  • Tight Coupling (তুলনামূলক): Subject-কে জানতে হয় যে Observer কোন ইন্টারফেস ইমপ্লিমেন্ট করেছে।

  • Synchronous Execution: সাধারণত এটি সিঙ্ক্রোনাসলি কাজ করে। অর্থাৎ, যতক্ষণ না সব অবসার্ভারের এক্সিকিউশন শেষ হচ্ছে, ততক্ষণ Subject ব্লকড থাকে।

  • Scope (পরিধি): এটি মূলত একই অ্যাপ্লিকেশন বা প্রসেসের মেমোরির ভেতরে কাজ করে।

  • ব্যবহার: GUI ফ্রেমওয়ার্কে বাটন ক্লিক ইভেন্ট, জ্যাঙ্গো (Django) সিগন্যালস বা স্প্রিংয়ের (Spring) অ্যাপ্লিকেশন ইভেন্টস।

Example: Observer Pattern

এখানে Java ব্যবহার করে অবসার্ভার প্যাটার্নের একটি প্রফেশনাল ইমপ্লিমেন্টেশন দেওয়া হলো। খেয়াল করো, NotificationService সরাসরি User অবজেক্টগুলোকে চেনে এবং তাদের মেথড কল করছে।

import java.util.ArrayList;
import java.util.List;

// Step 1: Observer Interface
interface Observer {
    void update(String message);
}

// Step 2: Concrete Observer
class User implements Observer {
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(this.name + " received: " + message);
    }
}

// Step 3: Subject (Maintains the direct list of observers)
class NotificationService {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyAllObservers(String message) {
        // Synchronously calling the update method on each observer
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// Step 4: Client
public class Main {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();
        
        User user1 = new User("Mominur");
        User user2 = new User("Rahim");

        service.addObserver(user1);
        service.addObserver(user2);

        service.notifyAllObservers("Today's news update is here!");
    }
}

Publisher-Subscriber (Pub-Sub) Pattern: মিডলম্যান আর্কিটেকচার

Pub-Sub প্যাটার্নে Publisher এবং Subscriber একে অপরকে একেবারেই চেনে না। তাদের মাঝখানে একটি তৃতীয় পক্ষ বা Middle Layer থাকে, যাকে 'Message Broker' বা 'Event Bus' বলা হয়।

Publisher শুধু জানে যে তাকে একটি নির্দিষ্ট 'Topic' বা 'Channel'-এ মেসেজ পাঠাতে হবে। মেসেজটি পাঠানোর পর তার কাজ শেষ (Fire and forget)। অন্যদিকে Subscriber-ও জানে না মেসেজটি কে পাঠিয়েছে; সে শুধু ব্রোকারকে বলে রাখে, "এই Topic-এ কোনো মেসেজ আসলে আমাকে দিও।"

বৈশিষ্ট্য (মিসিং কনসেপ্ট যুক্ত করা হলো):

  • Loose Coupling (সর্বোচ্চ লেভেল): কেউ কাউকে চেনে না, শুধু ব্রোকারকে চেনে।

  • Asynchronous Execution: এটি সাধারণত অ্যাসিনক্রোনাস (Asynchronous) হয়। মেসেজ ব্রোকারে মেসেজ রেখে Publisher নিজের অন্য কাজে চলে যেতে পারে।

  • Network/Distributed Systems: এটি মূলত ডিস্ট্রিবিউটেড সিস্টেম (Distributed Systems) বা মাইক্রোসার্ভিস (Microservices) আর্কিটেকচারে ব্যবহৃত হয়।

  • ব্যবহার: Apache Kafka, RabbitMQ, Redis Pub/Sub, AWS SNS/SQS।

Example: Pub-Sub Pattern

নিচে পাইথন (Python) ব্যবহার করে একটি কাস্টম EventBus তৈরি করে Pub-Sub প্যাটার্ন দেখানো হলো।

# Step 1: Message Broker / Event Bus
class EventBus:
    def __init__(self):
        # A dictionary to hold topics and their subscriber callbacks
        self.subscribers = {}

    def subscribe(self, topic: str, callback):
        if topic not in self.subscribers:
            self.subscribers[topic] = []
        self.subscribers[topic].append(callback)

    def publish(self, topic: str, data: str):
        # Publisher simply drops the message to the topic
        if topic in self.subscribers:
            for callback in self.subscribers[topic]:
                callback(data)

# Step 2: Client Usage
if __name__ == "__main__":
    broker = EventBus()

    # Subscriber callbacks (They don't know who publishes the news)
    def subscriber_one(msg: str):
        print(f"Subscriber 1 received: {msg}")

    def subscriber_two(msg: str):
        print(f"Subscriber 2 received: {msg}")

    # Subscribers register with the Broker on a specific topic
    broker.subscribe("news_topic", subscriber_one)
    broker.subscribe("news_topic", subscriber_two)

    # Publisher publishes to the Broker (It doesn't know who the subscribers are)
    broker.publish("news_topic", "Special breaking news arrived!")

কোর পার্থক্যসমূহ (Key Differences at a Glance)

বৈশিষ্ট

Observer Pattern

Pub-Sub Pattern

Coupling (নির্ভরশীলতা)

Subject এবং Observer একে অপরের সাথে সরাসরি যুক্ত (Tight Coupling)।

Publisher এবং Subscriber একে অপরকে চেনে না (Loose Coupling)।

Middleman (মধ্যস্থতাকারী)

কোনো ব্রোকার বা মধ্যস্থতাকারী নেই।

মেসেজ ব্রোকার বা ইভেন্ট বাস (Message Broker/Event Bus) থাকে।

Execution (কাজের ধরন)

সাধারণত সিঙ্ক্রোনাস (Synchronous)।

মূলত অ্যাসিনক্রোনাস (Asynchronous)।

Scope (ব্যবহারের ক্ষেত্র)

সিঙ্গেল অ্যাপ্লিকেশন বা একই মেমোরি স্পেসে ব্যবহৃত হয়।

মাইক্রোসার্ভিস বা ডিস্ট্রিবিউটেড সার্ভার কমিউনিকেশনে ব্যবহৃত হয়।

Topology (যোগাযোগের ধরন)

One-to-Many (একটি সাবজেক্ট, একাধিক অবসার্ভার)।

Many-to-Many (একাধিক পাবলিশার, একাধিক সাবস্ক্রাইবার)।

তুমি যখন একটি মনোলিথিক (Monolithic) অ্যাপ্লিকেশনের ভেতরে কম্পোনেন্টগুলোর মধ্যে যোগাযোগ স্থাপন করতে চাইবে, তখন Observer Pattern হলো বেস্ট চয়েস। কিন্তু তুমি যখন সিস্টেমকে স্কেল করে মাইক্রোসার্ভিসে নিয়ে যাবে এবং চাইবে একটি সার্ভিস (যেমন: Order Service) কাজ শেষ করে অন্য সার্ভিসকে (যেমন: Payment Service) জানাক—তখন তোমাকে অবশ্যই Kafka বা Redis-এর মতো মেসেজ ব্রোকার ব্যবহার করে Pub-Sub Pattern ইমপ্লিমেন্ট করতে হবে।