DEV Community

Wallace Freitas
Wallace Freitas

Posted on

Combining Repository and Event-Driven Patterns

An asynchronous response to data changes can be achieved in systems that abstract data persistence by combining the Repository Pattern with Event-Driven Architecture. This combination is especially helpful for systems that require certain actions, including updating external systems, logging changes, or sending notifications, to be initiated following database operations.

Now let's combine the two patterns into an example.

Step-by-Step Implementation:

1️⃣ Define a Repository for User Entities
2️⃣ Emit Events When Data Changes
3️⃣ React to Events Asynchronously

1. User Repository with Event Emission

We’ll extend the previous UserRepository example to emit events when certain actions, like user creation, take place.

class EventDrivenUserRepository extends InMemoryUserRepository {
  private eventEmitter: EventEmitter;

  constructor(eventEmitter: EventEmitter) {
    super();
    this.eventEmitter = eventEmitter;
  }

  async save(user: User): Promise<void> {
    await super.save(user);
    this.eventEmitter.emit('userCreated', user); // Emit event on user creation
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, we extend the InMemoryUserRepository to emit a userCreated event whenever a new user is saved.

2. Event Subscriber to Handle Business Logic

We can now define event subscribers that will listen for specific events and react asynchronously.

const eventEmitter = new EventEmitter();
const userRepository = new EventDrivenUserRepository(eventEmitter);

// React to the 'userCreated' event
eventEmitter.on('userCreated', (user: User) => {
  console.log(`New user created: ${user.name}`);
  // Perform additional tasks, like sending an email
});
Enter fullscreen mode Exit fullscreen mode

3. Combining Repository and Event-Driven in Practice

Let’s put everything together and simulate saving a user and reacting to the event:

(async () => {
  const user = { id: '2', name: 'Jane Smith', email: 'jane@example.com' };
  await userRepository.save(user);
})();
Enter fullscreen mode Exit fullscreen mode

In this scenario, saving a new user through the userRepository will trigger the userCreated event, and the subscriber will execute the associated logic (e.g., logging or sending notifications) asynchronously.


Advantages of Combining Repository and Event-Driven Patterns

👉🏻 Decoupled Architecture: The repository is responsible only for data access, while business logic (like sending notifications or processing external updates) is handled by the event listeners. This separation of concerns leads to more maintainable code.

👉🏻 Scalability: Asynchronous event handling allows the system to scale better. You can easily add more event listeners to react to different events without changing the core repository logic.

👉🏻 Flexibility: The system becomes more flexible, as you can add or remove event listeners without modifying the core business logic.

For example, you can add new actions triggered by user creation, such as notifying external services, without altering the user repository.


Conclusion

Repository Pattern and Event-Driven Architecture work together to create incredibly scalable and decoupled systems. Events enable asynchronous communication and system responses to state changes, while the repository offers an abstraction for data access. When combined, these patterns help you build code that is easier to maintain, more readable, and capable of handling reactive logic and real-time updates.

This combination is especially helpful for sophisticated, distributed systems where separation of responsibilities and asynchronous processing are crucial, regardless of whether you're developing microservices or a monolithic system.

Top comments (2)

Collapse
 
jangelodev profile image
João Angelo

Hi Wallace Freitas,
Thanks for sharing.

Collapse
 
wallacefreitas profile image
Wallace Freitas

Welcome João.