Reference architecture for using Event Driven .NET abstractions and libraries for Domain Driven Design (DDD), Command Query Responsibility Segregation (CQRS) and Event Driven Architecture (EDA).
- .NET Core SDK (8.0 or greater)
- Docker Desktop
- MongoDB Docker:
docker run --name mongo -d -p 27017:27017 -v /tmp/mongo/data:/data/db mongo
- MongoDB Client:
- Download Studio 3T.
- Add connection to localhost on port 27017.
- Dapr (Distributed Application Runtime)
- .NET Aspire Workload
dotnet workload update dotnet workload install aspire dotnet workload list
- Specflow IDE Plugin (recommended)
This project builds on the principles of Domain Driven Design (DDD) to provide a set of abstractions and reference architecture for implementing the Command Query Responsibility Segregation (CQRS) pattern. By providing an event bus abstraction over Dapr (Distributed Application Runtime), the reference architecture demonstrates how to apply principles of Event Driven Architecture (EDA). Because entities process commands by emitting domain events, adding event sourcing at a later time will be relatively straightforward.
Note: EventDriven.CQRS.Abstractions version 2.0 or later uses MediatR to enable a handler per command pattern with behaviors for cross-cutting concerns.
The Reference Architecture projects demonstrate how to apply these concepts to two microservices: CustomerService
and OrderService
. In addition, each service has separate controllers for read and write operations, thus segregating command and query responsibilities, with different sets of models, or Data Transfer Objects (DTO's).
- Command Controller: Converts DTO's to domain entities using AutoMapper. Passes commands to a command broker, which selects the appropriate command handler for executing business logic.
- Command Handlers: Uses a domain entity to process commands which generate one or more domain events, then requests entity to apply the domain events in order to mutate entity state. Persists entity state using a repository abstraction and optionally publishes an integration event which is handled by another microservice.
- Query Controller: Passes queries to a query broker, which selects the appropriate query handler to retrieve entities. Converts entities to DTO's with AutoMapper.
- Query Handlers: Processes queries to retrieve entities using a repository abstraction.
- Behaviors: Used to implement cross-cutting concerns such as logging or validation.
- Repository: Used to persist or retrieve entities from a database.
- Event Bus: Used to publish integration events, as well as subscribe to events using an event handler. Dapr is used to abstract away the underlying pub/sub implementation. The default is Redis (for local development), but Dapr can be configured to use other components, such as AWS SNS+SQS.
Note: This example illustrates a simple CQRS implementation with a shared database and single service for both read and write operations. A more sophisticated implementation might entail separate services and databases for read and write operations, using integration events to communicate between them. This simple example only uses integration events to communicate between the customer and order services.
- If using an IDE such as Visual Studio or Rider, Using an IDE such as Visual Studio or Rider, run the http profile of ReferenceArchitecture.AppHost.
- Open the Aspire dashboard to inspect service endpoints and view logs.
- Create some customers.
- Open http://localhost:5656/swagger
- Execute posts using contents of customers.json.
- Copy post response, modify fields, then execute puts.
- Make sure to copy
etag
value from last response, or you will get a concurrency error.
- Make sure to copy
- Copy
id
andetag
values to execute deletes. - Execute gets to retrieve customers.
- View customers database collections using Robo 3T.
- Create some orders.
- Open http://localhost:5757/swagger
- Execute posts using contents of orders.json.
- Copy post response, modify fields, then execute puts.
- Make sure to copy
etag
value from last response, or you will get a concurrency error.
- Make sure to copy
- Copy
id
andetag
values to execute deletes. - Execute gets to retrieve orders.
- View orders database collections using Robo 3T.
- Update the address of a customer who has order.
- Note the address is also updated for the customer's orders.
- Observe log messages in terminal when integration events are published and handled.
-
Start the Customer Service using the Dapr CLI from a terminal at the project root.
dapr run --app-id customer-service --app-port 5656 --resources-path ../dapr/components -- dotnet run
-
Start the Order Service using the Dapr CLI from a terminal at the project root.
dapr run --app-id order-service --app-port 5757 --resources-path ../dapr/components -- dotnet run
-
Open the Dapr Dashboard at http://localhost:8080
dapr dashboard
-
Execute steps 3-5 above to use services and pub-sub features.
In the test folder you'll find unit tests for both CustomerService and OrderService projects.
Note: Because database API's are notoriously difficult to mock, repositories are deliberately excluded from unit testing. Instead, repositories attain code coverage with acceptance/integration tests.
- Run unit CustomerService.Tests and OrderService.Tests from the Test explorer in your IDE.
- Alternatively, open a terminal at CustomerService.Tests and OrderService.Tests, then run
dotnet test
In the tests folder you'll find an EventDriven.ReferenceArchitecture.Specs project with automated acceptance / integration tests.
- SpecFlow is used as the acceptance testing framework.
- Feature files use Gherkin syntax to enable Behavior Driven Development with scenarios that match acceptance criteria in user stories.
- Using an IDE such as Visual Studio or Rider, run the specs profile of ReferenceArchitecture.AppHost.
- Run EventDriven.ReferenceArchitecture.Specs from the Test explorer of your IDE.
- Alternatively, open a terminal at EventDriven.ReferenceArchitecture.Specs, then run
dotnet test
For step-by-step instructions on how to build microservices with Event Driven .NET using this reference architecture, please see the Event Driven .NET Development Guide.