Understanding CQRS and Event Sourcing.

Davi Orlandi - Jul 15 - - Dev Community

Introduction

In modern software architecture, ensuring scalability, maintainability, and performance is crucial. Two patterns that help achieve these goals are CQRS (Command Query Responsibility Segregation) and Event Sourcing. This article explores these concepts, their benefits, and how they can be implemented.

What is CQRS?

CQRS stands for Command Query Responsibility Segregation. It is a pattern that separates read and write operations for a data store. Traditionally, CRUD (Create, Read, Update, Delete) operations are performed on a single model, which can lead to complexity and inefficiency, especially in large-scale systems.

Benefits of CQRS

  1. Scalability: By separating reads and writes, each can be scaled independently.
  2. Performance: Read models can be optimized for queries, and write models can be optimized for updates.
  3. Simplified Models: Separation of concerns makes the codebase cleaner and easier to maintain.

Implementing CQRS

In a CQRS system, commands (writes) and queries (reads) are handled by different models:

  • Command Model: Handles state-changing operations (e.g., create, update, delete).
  • Query Model: Handles read-only operations (e.g., fetch data).

Here's a basic example using TypeScript:

// Command Model
class CreateUserCommand {
    constructor(public name: string, public email: string) {}
}

class UserCommandHandler {
    handle(command: CreateUserCommand): void {
        // Logic to handle user creation
        // e.g., save to database
    }
}

// Query Model
class UserQuery {
    constructor(public id: string) {}
}

class UserQueryHandler {
    handle(query: UserQuery): User | null {
        // Logic to fetch user data
        // e.g., retrieve from database
        return { id: query.id, name: "John Doe", email: "john.doe@example.com" };
    }
}
Enter fullscreen mode Exit fullscreen mode

What is Event Sourcing?

Event Sourcing is a pattern where state changes are captured as a sequence of events. Instead of storing the current state, the system stores a series of events that describe state transitions.

Benefits of Event Sourcing

  1. Auditability: Every change is recorded, providing a complete audit trail.
  2. Event Replay: System state can be reconstructed by replaying events.
  3. Decoupling: Events can be used to integrate with other systems asynchronously.

Implementing Event Sourcing

In an event-sourced system, state changes are represented as events:

// Event
class UserCreatedEvent {
    constructor(public id: string, public name: string, public email: string) {}
}

// Event Store
class EventStore {
    private events: Array<any> = [];

    save(event: any): void {
        this.events.push(event);
    }

    getEvents(): Array<any> {
        return this.events;
    }
}

// Aggregate
class User {
    private id: string;
    private name: string;
    private email: string;

    constructor(private eventStore: EventStore) {}

    create(id: string, name: string, email: string): void {
        const event = new UserCreatedEvent(id, name, email);
        this.apply(event);
        this.eventStore.save(event);
    }

    private apply(event: UserCreatedEvent): void {
        this.id = event.id;
        this.name = event.name;
        this.email = event.email;
    }
}

// Usage
const eventStore = new EventStore();
const user = new User(eventStore);
user.create("1", "John Doe", "john.doe@example.com");

console.log(eventStore.getEvents());
Enter fullscreen mode Exit fullscreen mode

Combining CQRS and Event Sourcing

CQRS and Event Sourcing complement each other well. Event Sourcing naturally fits the write side of CQRS, where commands result in events that are stored and processed. The read side can use these events to build query models.

Example

// Command Model (Event Sourced)
class UserCommandHandler {
    constructor(private eventStore: EventStore) {}

    handle(command: CreateUserCommand): void {
        const event = new UserCreatedEvent(command.name, command.email);
        this.eventStore.save(event);
    }
}

// Query Model
class UserReadModel {
    private users: { [key: string]: User } = {};

    handleEvent(event: UserCreatedEvent): void {
        this.users[event.id] = new User(event.id, event.name, event.email);
    }

    getUser(id: string): User | null {
        return this.users[id] || null;
    }
}

// Usage
const eventStore = new EventStore();
const userCommandHandler = new UserCommandHandler(eventStore);
const userReadModel = new UserReadModel();

userCommandHandler.handle(new CreateUserCommand("John Doe", "john.doe@example.com"));
eventStore.getEvents().forEach(event => userReadModel.handleEvent(event));

console.log(userReadModel.getUser("1"));
Enter fullscreen mode Exit fullscreen mode
. . . .
Terabox Video Player