Event-Driven Architecture (EDA)
Relevant source files
The following files were used as context for generating this wiki page:
- claude.md
- BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs
- WebApplications/Billing.WebApi/Program.cs
- WebApplications/Scheduling.WebApi/Program.cs
- Aspire.AppHost/AppHost.cs
- Core/Scheduling/Scheduling.Infrastructure/Persistence/Migrations/SchedulingDbContextModelSnapshot.cs
- Core/Scheduling/Scheduling.Domain.Tests/ApplicationTests/EventHandlerTests/PatientCreatedEventHandlerTests.cs
The Event-Driven Architecture (EDA) in this project facilitates asynchronous communication between Bounded Contexts (microservices) such as Scheduling and Billing. It is a core pillar of the project's DDD Fundamentals and Integration strategy, allowing for decoupled, scalable, and resilient service interactions. The architecture supports both Domain Events (intra-context) and Integration Events (inter-context).
Sources: claude.md:46-56, claude.md:148-150
Architecture Overview
The system utilizes a hybrid approach for messaging, supporting both MassTransit (default) and Wolverine as messaging frameworks. These frameworks abstract the underlying message broker, which is typically RabbitMQ (or optionally Azure Service Bus).
Messaging Frameworks and Broker
The project is configured to use RabbitMQ as the primary message broker, managed via .NET Aspire for local development. For production-like environments or specific configurations, Azure Service Bus can be used as an alternative.
| Component | Description | Default / Implementation |
|---|---|---|
| Message Broker | The transport layer for messages. | RabbitMQ (Docker/Aspire) |
| Service Bus | Abstraction layer for publishing/subscribing. | MassTransit (Default) |
| Alternative Bus | MIT-licensed messaging alternative. | Wolverine |
| Orchestrator | Manages container lifecycle and discovery. | .NET Aspire |
Sources: claude.md:179-190, WebApplications/Billing.WebApi/Program.cs:51-68, Aspire.AppHost/AppHost.cs:46-56
Event Types and Flow
The architecture distinguishes between two primary types of events:
- Domain Events: Triggered by state changes within an Aggregate. Handled synchronously or asynchronously within the same Bounded Context using MediatR.
- Integration Events: Published to the message bus for consumption by other Bounded Contexts. These represent significant business occurrences that cross service boundaries.
Event Lifecycle and Transactional Integrity
The project implements a reliable event publishing flow using a combination of Domain Event dispatching and an Outbox Pattern. This ensures that database updates and event publishing happen atomically.
Diagram showing the transactional flow of domain and integration events within the Unit of Work.
Sources: BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:40-101, claude.md:47-49
Implementation Details
The Outbox Pattern
To prevent message loss or ghost messages, the system uses an Outbox. When using MassTransit, integration events are written to an OutboxMessage table in the service's database before being dispatched to RabbitMQ by a background service.
Database Schema (Scheduling Context)
| Table | Description |
|---|---|
Scheduling_OutboxMessage | Stores messages waiting to be delivered to the broker. |
Scheduling_OutboxState | Tracks the status and locks for outbox message delivery. |
Scheduling_InboxState | Ensures idempotent processing of incoming messages. |
Event Handlers and Mapping
Domain events are typically converted into Integration events within local handlers. For example, a PatientCreatedEvent (Domain) triggers a handler that queues a PatientCreatedIntegrationEvent (Integration) via the IUnitOfWork.
// Example from PatientCreatedEventHandler
public async Task Handle(PatientCreatedEvent domainEvent, CancellationToken ct)
{
var integrationEvent = new PatientCreatedIntegrationEvent(
domainEvent.PatientId,
domainEvent.FirstName,
domainEvent.LastName,
domainEvent.Email,
domainEvent.DateOfBirth);
_unitOfWork.QueueIntegrationEvent(integrationEvent);
}
Sources: Core/Scheduling/Scheduling.Domain.Tests/ApplicationTests/EventHandlerTests/PatientCreatedEventHandlerTests.cs:33-46, BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:103-128
Configuration and Integration
Multi-Framework Support
Services can choose between Wolverine and MassTransit via configuration. This is demonstrated in the Billing.WebApi setup:
Flowchart of the conditional messaging framework registration in service startup.
Sources: WebApplications/Billing.WebApi/Program.cs:51-68, WebApplications/Scheduling.WebApi/Program.cs:43-48
Infrastructure Orchestration
.NET Aspire manages the RabbitMQ resource and injects connection strings into the dependent Web APIs. It also provides a management plugin for observability.
Sources: Aspire.AppHost/AppHost.cs:113-130, claude.md:195-201
Conclusion
The Event-Driven Architecture ensures that bounded contexts remain isolated while maintaining eventual consistency. By utilizing the Outbox pattern through EfCoreUnitOfWork and supporting multiple messaging frameworks, the project provides a robust foundation for distributed transactions and scalable microservices communication.
Sources: claude.md:216-222, BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:130-165