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.
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
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
- Publisher publishes EDADemoOrderPlacedEvent
- RabbitMQ receives the event
- Invoice Consumer processes invoice logic
- Email Consumer sends email notification
- 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.