A .NET 8 microservices architecture demonstrating Clean Architecture, Domain-Driven Design (DDD), and CQRS with automated architecture tests, integration tests, and event-driven distributed coordination. This repository provides a complete, functioning microservices implementation that teams can learn from and adapt.
Repository: CleanArchitecture-DDD-CQRS-Microservices on GitHub
Original Template: CleanArchitecture-DDD-CQRS
This repository contains a Computerized Maintenance Management System (CMMS) implemented as microservices. It demonstrates how to build maintainable, testable, and scalable microservices using Clean Architecture, DDD, and CQRS patterns.
The CMMS domain is perfect for demonstrating microservices patterns because it has:
This repository demonstrates that implementing microservices with Clean Architecture, DDD, and CQRS doesn't have to be complex. It shows how to apply these patterns pragmatically in .NET—with enough structure to maintain boundaries and enable testing, but without over-engineering.
Perfect for:
This repository includes implementations of enterprise patterns working together in a microservices architecture:
IDomainEventHandler (transactional) + IIntegrationEventHandler (async)
The system architecture demonstrates a microservices implementation with four main services:
Each service follows Clean Architecture with Domain, Application, Infrastructure, and API layers. Services maintain their own databases (WorkOrdersDb, AssetsDb, TechniciansDb, OrchestrationDb) ensuring service autonomy and independent scaling.
Services communicate asynchronously via events through the message bus. The Orchestration Service manages distributed workflows using the Saga pattern.

This diagram illustrates the complete flow for assigning a technician to a work order. The flow demonstrates the outbox pattern for guaranteed delivery, saga orchestration for distributed transactions, and eventual consistency across services.
The repository includes many architecture tests that automatically enforce DDD principles and Clean Architecture boundaries across all services. New team members can work confidently—architectural violations are caught at compile-time through automated tests.
Architecture tests are available as a reusable NuGet package (CleanArchitecture.Core.ArchitectureTests) with base test classes that can be inherited in your projects.
Immutability & Encapsulation:
ValueObjects_Should_Be_Immutable - No public setters allowed (init-only setters are OK)ValueObjects_Should_Be_Sealed - Prevents inheritance and maintains invariantsAggregates_Should_Have_Internal_Or_Private_Constructors - Enforces factory methodsType Safety:
DomainEvents_Should_Be_Sealed_And_EndWith_Event - Naming conventions enforcedDomain_Types_Should_Be_Internal - Prevents domain leakage to outer layersDomain_Should_Not_Depend_On_Other_Layers - Dependency rule enforcementLayer Isolation:
Application_Should_Not_Depend_On_Infrastructure_Or_Api - Clean Architecture enforcementCommands_And_Queries_Should_Be_Immutable - CQRS contracts are immutableRead/Write Separation:
QueryHandlers_Should_Not_Use_IRepository - Queries forbidden from using write-side repositoriesCommandHandlers_Should_Not_Use_IReadRepository - Commands forbidden from using read-side repositoriesDTO Boundaries:
Handlers_Should_Not_Return_Domain_Types - Handlers must return DTOs, never domain entitiesBounded Context Isolation:
Application_Features_Should_Be_BoundedContexts - Features cannot depend on other featuresFor New Developers:
For Teams:
For Code Reviews:
This repository evolved from the original CleanArchitecture-DDD-CQRS template. The original template was designed with microservices in mind from the start, which made the evolution straightforward.
1. Outbox Pattern Abstraction
The original template implemented IOutboxStore and IOutboxPublisher abstractions. This made swapping from in-process handlers to message bus integration trivial. The same code that worked in-process now publishes to RabbitMQ via MassTransit.
2. Event-Driven Architecture
The dual handler system (IDomainEventHandler for transactional events, IIntegrationEventHandler for async events) translated directly to microservices. Integration events that were handled in-process now flow through the message bus to other services.
3. Bounded Context Isolation
Architecture tests enforced strict boundaries between bounded contexts. When we split into services, these boundaries were already respected, preventing cross-service coupling.
4. Database-Per-Service Structure
The original template used separate database schema for each domain, and with no direct reference between domains/schemas. This structure naturally evolved into database-per-service.
5. Clean Separation of Concerns
Domain, Application, and Infrastructure layers were clearly separated. Each service maintains this structure independently.
6. Architecture Tests
Automated tests enforced boundaries at compile-time. These same tests now ensure services don't accidentally depend on each other's internals.
The original CleanArchitecture-DDD-CQRS template provides the foundation for how DDD and CQRS are built. We used the core NuGet packages from there for the abstractions and bootstrapping template code. For detailed documentation on DDD patterns, CQRS implementation, architecture tests, and other template features, see the original template repository.
We use MassTransit as a bridge on top of our outbox pattern. The CleanArchitecture.Outbox.MassTransit.Bridge package implements IOutboxPublisher to publish events to RabbitMQ.
Why MassTransit?
The outbox pattern abstraction from the original template made this integration straightforward. We swapped the in-process publisher for a MassTransit publisher without changing any business logic.
The CleanArchitecture.Cmms.Contracts project contains immutable event schemas shared across services:
WorkOrders.Events - Events published by WorkOrders serviceAssets.Events - Events published by Assets serviceTechnicians.Events - Events published by Technicians serviceAll services reference this project to ensure consistent message contracts. Events are versioned and immutable once published.
This ensures guaranteed delivery with at-least-once semantics.
The Orchestration service manages distributed workflows using the saga pattern. It coordinates operations that span multiple services and handles eventual consistency.
Sagas manage long-running transactions across services like:
Operations that require coordination across services are eventually consistent:
When operations fail, the saga publishes compensation like for example:
WorkOrderCompletingFailedEvent - Triggers rollback in participating servicesRevertTechnicianAssignmentRequestedEvent - Reverts technician assignmentThis ensures system consistency even when operations fail partway through.
Sagas are implemented using MassTransit state machines, which provide:
One challenge in microservices is querying data across service boundaries. Each service has its own database, so traditional joins aren't possible.
In WorkOrderReadRepository.cs, we need to display work orders with technician and asset names. In a monolith, this would be a simple join. In microservices, the data lives in different databases.
Current Approach (TODO):
The implementation returns empty strings for cross-service data fields. This acknowledges the limitation while we evaluate solutions.
Future Approach:
CDC (Change Data Capture) with Debezium streaming from SQL Server to Elasticsearch. This provides:
We have a separate demo repository showing the CDC approach with Debezium.
Each service owns its data. This ensures:
Services communicate via events, not direct calls:
Integration events written to outbox in same transaction:
Complex workflows coordinated by orchestration service:
Immutable event contracts shared across services:
The original template's ADRs (ADR-001 through ADR-006) documented foundational patterns that enabled this migration.
New ADRs for microservices:
The easiest way to run all services:
git clone https://github.com/mohd2sh/CleanArchitecture-DDD-CQRS-Microservices.git
cd CleanArchitecture-DDD-CQRS-Microservices
docker-compose up
Access:
Run services individually:
# WorkOrders Service
cd src/services/WorkOrders.Service/CleanArchitecture.Cmms.Api.WorkOrders
dotnet run
# Assets Service
cd src/services/Assets.Service/CleanArchitecture.Cmms.Api.Assets
dotnet run
# Technicians Service
cd src/services/Technicians.Service/CleanArchitecture.Cmms.Api.Technicians
dotnet run
Each service requires:
Each service has unit tests for:
Run all unit tests:
dotnet test --filter "FullyQualifiedName~UnitTests"
Integration tests use Testcontainers for:
Run all integration tests:
dotnet test --filter "FullyQualifiedName~IntegrationTests"
Automated tests enforce architectural boundaries at compile-time. These tests run as part of the unit test suite and prevent:
This repository focuses on architecture and design. Cross-cutting concerns that are out of scope:
Future improvements and enhancements:
Contributions welcome. This repository serves as an architecture reference and learning resource.
MIT License - see LICENSE file for details.
Built to demonstrate microservices architecture patterns in .NET