DEV Community

Cover image for Real-Time Notifications with the Observer Pattern: A Practical Guide
Sudharshan Madhavan
Sudharshan Madhavan

Posted on

Real-Time Notifications with the Observer Pattern: A Practical Guide

Introduction

In today's fast-paced world, data is a critical asset for everyone. Real-time notifications have become essential as they facilitate timely access to this data. In software development, ensuring efficient and prompt notifications is vital. The Observer pattern is an ideal solution for effectively managing real-time updates.

Why the Observer Pattern?

The Observer pattern is ideal for real-time notifications because it allows a subject to efficiently notify multiple observers about state changes, ensuring timely and scalable updates. This pattern promotes loose coupling, making it easy to add or remove observers without significant code modifications. The advantages of using the Observer pattern are:

  • One-to-Many Relationship: A single subject can notify multiple observers (subscribers).
  • Loose Coupling: Easily add or remove observers without altering the subject.
  • Automatic Updates: Observers are automatically notified of changes, ensuring timely updates.

Real-Time Applications of the Observer Pattern

The Observer pattern is commonly used in:

  • Weather Applications: Sending push notifications to users with weather updates.
  • Social Media: Notifying users about likes, comments, or messages in real time.
  • E-commerce "Notify Me" Features: Alerting users when out-of-stock items become available again.

Real Time Images

These examples demonstrate the versatility and importance of the Observer pattern in ensuring timely and relevant notifications, which aligns perfectly with my requirements for developing a notification system for a newsletter.

My Requirements: Developing a Notification System for a Newsletter

I was assigned the task of developing a newsletter system to send email notifications after each production release. These notifications would include major release items, a link to view all released items, and instructions on how to use the new features. The goals were to create a notification system that:

  • Scales Easily: Accommodates new subscribers without extensive code changes.
  • Offers Flexibility: Allows subscribers to choose their preferred notification method (email).
  • Ensures Efficiency: Sends timely notifications after production releases.

Solution

High-Level Overview

The Observer pattern structures the notification system with:

  • Subject Interface: Defines all the necessary methods to add, remove, and notify subscribers.
  • Subject or Event Source (ProductionRelease): Generates events (state changes) and maintains a list of observers interested in those events.
  • Observers Interface: Defines the method to trigger the notification to its subscribers.
  • Observers or Event Listeners (Subscribers): Add the observers to the observer list maintained by the Subject to receive notifications about events.

Here is the UML representation of how the observer pattern would look like:

UML Observer Pattern

Low-Level Implementation

Step 1: Create the Subject Interface and Implementation

Define the subject with methods to add, remove, checkForUpdates, and notify observers.

// ObservableInterface.ts
export interface ObservableInterface {
    add(observer: ObserverInterface): void;
    remove(observer: ObserverInterface): void;
    checkForUpdates(): void;
    notify(): void;
}

// ProductionRelease.ts
export class ProductionRelease implements ObservableInterface {

    private observerList: ObserverInterface[] = [];

    // Add a new observer to the list
    add(observer: ObserverInterface): void {
        this.observerList.push(observer);
    }

    // Remove an observer from the list
    remove(observer: ObserverInterface): void {
        const index = this.observerList.indexOf(observer);
        if (index !== -1) {
            this.observerList.splice(index, 1);
        }
    }

    // Check for updates and notify observers
    checkForUpdates(): void {
       //If new release then notify the users
        this.notify();
    }

    // Notify all observers about the update
    notify(): void {
        for (const observer of this.observerList) {
            observer.update();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Observer Interface and Implementations

Define how subscribers receive notifications via email.

// ObserverInterface.ts
export interface ObserverInterface {
    update(): void;
}

export class EmailSubscriberObserver implements ObserverInterface {

    private emailSubscribersList: Set<string> = new Set();

    subscribe(emailId: string) {
        // Add the new email to the subscription list and notify the subscriber
        this.emailSubscribersList.add(emailId);
        this.sendEmail(emailId, 'Your newsletter subscription was successful. You will receive updates soon after the release. Stay tuned!!!');
    }

    // Update method called by the subject to notify the observer
    update(): void {
        // Fetch the list of email subscribers from the set
        const emailListArray = Array.from(this.emailSubscribersList);
        for (const email of emailListArray) {
            this.sendEmail(email, 'Yes!!!! We have launched a new release of our software. Please click on the below link to get a detailed view of what this release is about.');
        }
    }

    // Unsubscribe an email from the list
    unSubscribe(emailId: string): void {
        this.emailSubscribersList.delete(emailId);
        this.sendEmail(emailId, 'You have unsubscribed from our newsletter. Please let us know if there is any feedback from your side.');
    }

    // Private method to simulate sending an email
    private sendEmail(email: string, emailBody: string): void {
        // Implement the email send logic
        console.log(`Email sent to ${email}: ${emailBody}`);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement Main Functionality

Instantiate the subject and observers, and trigger notifications.

// Main.ts
import { ProductionRelease } from './ProductionRelease';
import { EmailSubscriberObserver } from './EmailObserver';
import { ObservableInterface } from './ObservableInterface';
import { ObserverInterface } from './ObserverInterface';

// Create an Observable class with an object set to the instance of ProductionRelease
const productionReleaseObservable: ObservableInterface = new ProductionRelease();

// Create an Observer class object
const emailObserverObj: EmailSubscriberObserver = new EmailSubscriberObserver();

// Create all the email subscribers for the EmailSubscriberObserver
emailObserverObj.subscribe('soos@gmail.com');
emailObserverObj.subscribe('soos123@gmail.com');
emailObserverObj.subscribe('soos_other@gmail.com');

// Unsubscribe one of the subscribers
emailObserverObj.unSubscribe('soos123@gmail.com');

// Add the list of observers to the main observable object
productionReleaseObservable.add(emailObserverObj);

/* We can add more observers ex: smsObserverObj, pushNotificationObj etc
productionReleaseObservable.add(smsObserverObj);
productionReleaseObservable.add(pushNotificationObj);
*/

// Check for updates which will trigger notifications to all subscribers
productionReleaseObservable.checkForUpdates();
Enter fullscreen mode Exit fullscreen mode

Output

Output Reference

Conclusion

The Observer pattern is a robust choice for creating a newsletter system that handles real-time notifications efficiently. Its scalability and flexibility make it ideal for managing notifications with minimal code changes.

References

  • Observer Design Pattern Explanation - YouTube
  • Observer Pattern from Refactor Guru - Refactoring Guru
  • "Head First Design Patterns" by Eric Freeman and Elisabeth Robson

Thank you for taking the time to read this article. If you found it helpful, please give it a like. Feel free to comment if you notice anything missing or have suggestions for improvement. Your feedback will help enhance the content and benefit future readers.

Top comments (1)

Collapse
 
rkj180220 profile image
Ramkumar Jayakumar

Neat explanation!!