How Modular Monolits Architecture Handles Async Communications Between Modules

Mehmet Ozkaya
5 min readSep 3, 2024

--

We’re going to dive into how Modular Monolithic Architecture handles asynchronous communication between its modules.

Async Communications Between Modules

Get Udemy Course with limited discounted coupon — .NET Backend Bootcamp: Modulith, VSA, DDD, CQRS and Outbox

This is an important concept for building scalable, resilient, and maintainable applications. Let’s break it down! 🚀

Why Use Asynchronous Communication?

Asynchronous communication allows different parts of an application to operate independently and interact without waiting for each other to finish their tasks. This has several key benefits:

  1. Decoupling: By decoupling the sender and receiver, modules can function independently. This means one module can continue its operations without needing to wait for another to complete, making the system more flexible and modular.
  2. Scalability: When modules do not need to wait for responses, they can handle more requests and tasks concurrently. This is crucial for scaling applications to handle high loads efficiently.
  3. Resilience: If a module fails, asynchronous communication ensures that the messages or events can be retried or processed later. This improves fault tolerance and system reliability.
  4. Responsiveness: Users get immediate feedback because operations can be offloaded to background processes. This improves the user experience, especially for long-running tasks.

The Problem with Synchronous Communication

Synchronous communication can lead to a chain of dependencies where one module waits for another, creating a tightly coupled system. This can cause delays and reduce the overall responsiveness of the application. To avoid these issues, we need to explore asynchronous communication.

Event-Driven Architecture

In a Modular Monolith, asynchronous communication is often achieved through an event-driven architecture. Here’s how it works:

  1. Modules Communicate by Publishing and Subscribing to Events: Each module can publish events when certain actions occur. Other modules can subscribe to these events and react accordingly. This decouples the modules and allows for asynchronous processing.
  2. Message Brokers: Tools like RabbitMQ, Kafka, or in-memory message buses are commonly used to facilitate message passing between modules. These brokers ensure that messages are delivered reliably and can handle large volumes of messages efficiently.

Implementing Asynchronous Communication

So, how does asynchronous communication start in our application? Let’s go through the steps:

Step 1: Domain Events Lead to Integration Events

In modular monolithic architecture, communication often starts with Domain Events:

Domain Events Lead to Integration Events
  • Domain Events: These are events that indicate something significant has happened within a module, such as “OrderPlaced” or “UserRegistered”. These events are typically internal to a module and trigger specific business logic.

For example, in our application, the Catalog module raises a ProductPriceChanged domain event whenever a product’s price is updated.

  • Integration Events: These events are meant to notify other modules about changes or actions, such as “ProductPriceUpdated” or “OrderShipped”. Integration events are used for inter-module communication, allowing modules to stay in sync without direct dependencies.

When a ProductPriceChanged domain event occurs, it is handled and published as an Integration Event to notify other modules, like the Basket module, about the price change.

Step 2: Using In-Memory Service Bus That Can Shift to Message Broker

To implement asynchronous communication in our application, we can use an In-Memory Service Bus. This is a lightweight solution for development and testing that can be easily transitioned to a more robust Message Broker like RabbitMQ or Kafka in production.

Async Communications Between Modules

MassTransit

One popular .NET library for message-based communication is MassTransit. It supports various message brokers and provides a straightforward way to set up messaging in your application.

In-Memory Service Bus

For simplicity, during the development phase, we use an in-memory message bus. This simulates async communication without needing to set up external infrastructure. Here’s a basic setup:

  • Catalog Module: Publishes an event when a product’s price changes.
  • Basket Module: Subscribes to the event and processes it asynchronously.

By using an in-memory bus, we can keep our development environment simple while still implementing robust async communication patterns.

Benefits of Asynchronous Communication

  1. Improved Decoupling: Modules can evolve independently without tightly coupled dependencies. This makes the application more modular and easier to maintain.
  2. Enhanced Scalability: Modules can handle higher loads by processing messages asynchronously. This is crucial for applications that need to scale to handle thousands or millions of requests.
  3. Increased Resilience: Failures in one module do not propagate to others, improving the overall stability of the system. Messages can be retried or processed later if something goes wrong, ensuring reliable operation.

Conclusion

Asynchronous communication is a powerful tool in modular monolithic architectures. By decoupling modules and allowing them to communicate through events, we achieve a more scalable, resilient, and maintainable system. Tools like MassTransit make it easier to implement and manage asynchronous messaging, paving the way for a smoother transition to microservices in the future.

Get Udemy Course with limited discounted coupon — .NET Backend Bootcamp: Modulith, VSA, DDD, CQRS and Outbox

EShop Modular Monoliths Architecture w/ Catalog, Basket, Identity and Ordering modules

This is step-by-step development of reference Modular Monoltihs Architecture on .NET used ASP.NET Web API, Docker, PostgreSQL, Redis, RabbitMQ, Keycloak, Seq, MassTransit, Entity Framework Core, CQRS, MediatR, DDD, Vertical Slice Architecture and Outbox Pattern implementation with using latest features of .NET 8 and C# 12.

--

--

Mehmet Ozkaya

Software Architect | Udemy Instructor | AWS Community Builder | Cloud-Native and Serverless Event-driven Microservices https://github.com/mehmetozkaya