Logo

dev-resources.site

for different kinds of informations.

Understanding Domain Events in TypeScript: Making Events Work for You

Published at
9/8/2024
Categories
typescript
domaindrivendesign
domainevents
patterns
Author
v1eira
Author
6 person written this
v1eira
open
Understanding Domain Events in TypeScript: Making Events Work for You

Photo by [AltumCode](https://unsplash.com/@altumcode?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)

The Lowdown on Domain Events

When we talk about Domain-Driven Design (DDD), Domain Events serve as messengers, announcing significant changes within the system. These events act as crucial bridges, allowing different parts of your application to stay informed about relevant occurrences, fostering decoupling and maintainability.

What Are Domain Events?

Domain Events are lightweight, immutable objects representing state changes or occurrences within your application’s domain. They encapsulate essential information about what happened without carrying the weight of complex logic. Examples could range from a new user registration to an order being shipped.

Creating Domain Events in TypeScript

In TypeScript, creating a Domain Event is straightforward. Let’s consider a ProductCreatedEvent:

import EventInterface from "../../@shared/event/event.interface";

export default class ProductCreatedEvent implements EventInterface {
    dataTimeOccurred: Date;
    eventData: any;

    constructor(eventData: any) {
        this.dataTimeOccurred = new Date();
        this.eventData = eventData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the event captures whatever data we want to register and the exact time it happened. It’s a simple data structure designed for communication.

Handlers and Why They Matter

Event Handlers are responsible for reacting to Domain Events. They encapsulate the logic triggered by an event. For instance, when a ProductCreatedEvent occurs, you might want to send an email or log the event.

import EventHandlerInterface from "../../../@shared/event/event-handler.interface";
import ProductCreatedEvent from "../product-created.event"

export default class SendEmailWhenProductIsCreatedHandler implements EventHandlerInterface<ProductCreatedEvent> {
    handle(event: ProductCreatedEvent): void {
        console.log("Sending email to ...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Handlers keep your code modular and focused. Each handler takes care of a specific event, avoiding clutter and maintaining clarity.

Dispatching Events

Event Dispatchers act as messengers, delivering events to their respective handlers. Dispatching involves notifying interested parties that an event has occurred, triggering the associated logic. Here’s a simple event dispatcher:

import EventDispatcherInterface from "./event-dispatcher.interface";
import EventHandlerInterface from "./event-handler.interface";
import EventInterface from "./event.interface";

export default class EventDispatcher implements EventDispatcherInterface {
    private eventHandlers: { [eventName: string]: EventHandlerInterface[] } = {};

    get getEventHandlers(): { [eventName: string]: EventHandlerInterface[] } {
        return this.eventHandlers;
    }

    notify(event: EventInterface): void {
        const eventName = event.constructor.name;
        if (this.eventHandlers[eventName]) {
            this.eventHandlers[eventName].forEach((eventHandler) => {
                eventHandler.handle(event);
            });
        }
    }

    register(eventName: string, eventHandler: EventHandlerInterface<EventInterface>): void {
        if (!this.eventHandlers[eventName]) {
            this.eventHandlers[eventName] = [];
        }
        this.eventHandlers[eventName].push(eventHandler);
    }

    unregister(eventName: string, eventHandler: EventHandlerInterface<EventInterface>): void {
        if (this.eventHandlers[eventName]) {
            const index = this.eventHandlers[eventName].indexOf(eventHandler);
            if (index !== -1) {
                this.eventHandlers[eventName].splice(index, 1);
            }
        }
    }

    unregisterAll(): void {
        this.eventHandlers = {};
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the EventDispatcher maintains a collection of event handlers and is responsible for registering, unregistering and dispatching events accordingly.

Putting It All Together

Let’s bring these concepts together with a practical example:

it("Should notify all event handlers", () => {
    const eventDispatcher = new EventDispatcher();
    const eventHandler = new SendEmailWhenProductIsCreatedHandler();
    const spyEventHandler = jest.spyOn(eventHandler, "handle");

    eventDispatcher.register("ProductCreatedEvent", eventHandler);

    expect(eventDispatcher.getEventHandlers["ProductCreatedEvent"][0]).toMatchObject(eventHandler);

    const productCreatedEvent = new ProductCreatedEvent({
        name: "Product 1",
        description: "Product 1 description",
        price: 10.0
    });

    // When notify is executed SendEmailWhenProductIsCreatedHandler.handle() must be called
    eventDispatcher.notify(productCreatedEvent);

    expect(spyEventHandler).toHaveBeenCalled();
});
Enter fullscreen mode Exit fullscreen mode

In this scenario, when the ProductCreatedEvent is dispatched, the associated handler (SendEmailWhenProductIsCreatedHandler) reacts by logging message.

By embracing Domain Events, Handlers, and Dispatchers, you enhance the flexibility, maintainability, and scalability of your applications. These practices encourage a loosely coupled architecture, allowing you to adapt and extend your system more efficiently as your domain evolves.

patterns Article's
30 articles in total
Favicon
Streamlining Data Flow in Angular: The Power of the Adapter Pattern 🔄
Favicon
CQRS — Command Query Responsibility Segregation — A Java, Spring, SpringBoot, and Axon Example
Favicon
Mastering the Container-Presenter Pattern in Angular: A Deep Dive
Favicon
Repository Design Pattern, Promoting Packages via ADS, and New Arabic Article ✨
Favicon
Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns
Favicon
Understanding Domain Events in TypeScript: Making Events Work for You
Favicon
Padrões de Projeto em React [HOCs, Render Props, Hooks]
Favicon
Mobile Development Platforms and software architecture pattern in mobile development
Favicon
Finite-state machine example in JavaScript
Favicon
OOP Simplified: Quick Factory Methods with Encapsulation, Abstraction, and Polymorphism in TypeScript
Favicon
Finite-state machine example in JavaScript
Favicon
How to avoid N + 1 and keep your Ruby on Rails controller clean
Favicon
Types Of Software Architecture
Favicon
Testando das trincheiras: Usando um "clock" fixo
Favicon
¿POR QUÉ no estás usando estos providers de Angular?
Favicon
Common and Useful Deployment Patterns
Favicon
Reusing state management: HOC vs Hook
Favicon
Understanding JavaScript Creational Patterns for Object Creation
Favicon
Understanding Design First: Principles, Patterns, and Best Practices Explained
Favicon
The Architecture Might Not Be the Problem
Favicon
Padrão JumpTable com Javascript
Favicon
Explorando os Fundamentos dos Padrões de Projeto: Conceitos Básicos
Favicon
Six Factors That Raise The Risk Of Bugs In A Codebase
Favicon
Microservices: Avoiding the Pitfalls, Embracing the Potential - A Guide to Anti-Patterns
Favicon
Android Presentation Patterns: MVI
Favicon
Entendendo o Pub/Sub com Javascript
Favicon
The Consumer Conundrum: Navigating Change in Microservices Without Gridlock
Favicon
𝗪𝗵𝗮𝘁 𝗮𝗿𝗲 𝘁𝗵𝗲 𝗥𝗲𝗴𝗘𝘅 𝗣𝗮𝘁𝘁𝗲𝗿𝗻𝘀?
Favicon
Using a Trait in Builder CLIs
Favicon
CLI Contexts

Featured ones: