Event-Driven Architecture with MassTransit and RabbitMQ

• By OmerZ Solutions

Modern distributed systems require services to communicate asynchronously without creating tight dependencies between applications.

Event-Driven Architecture (EDA) solves this problem by allowing services to communicate through events using a message broker such as RabbitMQ.

Event Driven Architecture with MassTransit and RabbitMQ
Event-Driven Architecture (EDA) enables multiple independent services to react to the same event asynchronously, improving scalability and maintainability.

Technologies Used

  • ASP.NET Core
  • MassTransit
  • RabbitMQ
  • Publisher/Subscriber Pattern

Solution Overview

In this implementation, we create:

  • 1 Publisher Application
  • 2 Consumer Applications
  • RabbitMQ Message Broker

The Publisher publishes an event named EDADemoOrderPlacedEvent.

Two independent consumers subscribe to this event:

  • Invoice Consumer
  • Email Consumer

Prerequisites

RabbitMQ Server must be installed and running before executing the application.

Default RabbitMQ Credentials:

Host: localhost
Username: guest
Password: guest

Shared Event Contract

The event contract should be placed inside a separate Class Library so all projects can reference the same event model.

public record EDADemoOrderPlacedEvent
{
    public Guid OrderID { get; init; }

    public string OrderName { get; init; } = string.Empty;

    public string CustomerName { get; init; } = string.Empty;

    public decimal TotalAmount { get; init; }
}

Publisher Implementation

The Publisher application publishes EDADemoOrderPlacedEvent to RabbitMQ.

Required NuGet Packages

dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQ
dotnet add package Microsoft.Extensions.Hosting

Publisher Program.cs

public static async Task Main(string[] args)
{
    var host = Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services.AddMassTransit(x =>
            {
                x.UsingRabbitMq((ctx, cfg) =>
                {
                    cfg.Host("localhost", "/", h =>
                    {
                        h.Username("guest");
                        h.Password("guest");
                    });
                });
            });
        })
        .Build();

    await host.StartAsync();

    var publisherEDADemo =
        host.Services.GetRequiredService<IPublishEndpoint>();

    Console.WriteLine(
        "Press 'q' to quit, or any other key to publish an EDADemoOrderPlacedEvent.");

    while (Console.ReadKey(true).Key != ConsoleKey.Q)
    {
        var orderEvent = new EDADemoOrderPlacedEvent
        {
            OrderID = Guid.NewGuid(),
            OrderName = "Pizza",
            CustomerName = "Omer Javed",
            TotalAmount = 10.58m
        };

        await publisherEDADemo.Publish(orderEvent);

        Console.WriteLine(
            $"Published EDADemoOrderPlacedEvent: OrderID={orderEvent.OrderID}");
    }

    await host.StopAsync();
}

Invoice Consumer

The Invoice Consumer subscribes to the EDADemoOrderPlacedEvent.

Whenever the event is published, this consumer generates invoice-related processing.

Invoice Consumer Class

public class EDADemoInvoiceConsumer :
    IConsumer<EDADemoOrderPlacedEvent>
{
    public Task Consume(
        ConsumeContext<EDADemoOrderPlacedEvent> context)
    {
        var orderEvent = context.Message;

        Console.WriteLine(
            $"OrderID: {orderEvent.OrderID}, " +
            $"OrderName: {orderEvent.OrderName}, " +
            $"CustomerName: {orderEvent.CustomerName}, " +
            $"TotalAmount:{orderEvent.TotalAmount}");

        return Task.CompletedTask;
    }
}

Invoice Consumer Program.cs

services.AddMassTransit(x =>
{
    x.AddConsumer<EDADemoInvoiceConsumer>();

    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("localhost", "/", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });

        cfg.ReceiveEndpoint("invoice-service", e =>
        {
            e.ConfigureConsumer<EDADemoInvoiceConsumer>(ctx);
        });
    });
});

Email Consumer

The Email Consumer also subscribes to the same event.

This demonstrates one of the major benefits of Event-Driven Architecture: multiple independent services can react to the same event.

Email Consumer Class

public class EDADemoEmailConsumer :
    IConsumer<EDADemoOrderPlacedEvent>
{
    public Task Consume(
        ConsumeContext<EDADemoOrderPlacedEvent> context)
    {
        var orderEvent = context.Message;

        Console.WriteLine(
            $"Email is sent for OrderID: {orderEvent.OrderID}, " +
            $"OrderName: {orderEvent.OrderName} and " +
            $"CustomerName: {orderEvent.CustomerName} " +
            $"with TotalAmount:{orderEvent.TotalAmount}");

        return Task.CompletedTask;
    }
}

Email Consumer Program.cs

services.AddMassTransit(x =>
{
    x.AddConsumer<EDADemoEmailConsumer>();

    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("localhost", "/", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });

        cfg.ReceiveEndpoint("email-service", e =>
        {
            e.ConfigureConsumer<EDADemoEmailConsumer>(ctx);
        });
    });
});

How Event Flow Works

  1. Publisher publishes EDADemoOrderPlacedEvent
  2. RabbitMQ receives the event
  3. Invoice Consumer processes invoice logic
  4. Email Consumer sends email notification
  5. Both services execute independently

Advantages of Event-Driven Architecture

  • Loose coupling between services
  • Improved scalability
  • Asynchronous communication
  • Better maintainability
  • Independent service deployment
  • Easy integration with microservices

Real-World Use Cases

  • E-Commerce Platforms
  • Payment Processing Systems
  • Order Management Systems
  • Email Notification Services
  • Inventory Management
  • Microservices Architecture

Conclusion

MassTransit combined with RabbitMQ provides a clean and scalable approach for implementing Event-Driven Architecture in ASP.NET Core applications.

This architecture allows multiple independent services to process events asynchronously without direct dependencies between applications.

As enterprise applications grow, Event-Driven Architecture becomes extremely valuable for scalability, flexibility, and maintainability.

Need Help Building Scalable .NET Applications?

OmerZ Solutions helps businesses build scalable, secure, and enterprise-grade ASP.NET Core applications.

Contact Us