Microservices Clean Architecture CQRS DDD

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

Introduction

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:

Philosophy

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:

Design Principles

Key Features Overview

This repository includes implementations of enterprise patterns working together in a microservices architecture:

Core Architecture

Microservices Patterns

Event-Driven Architecture

Reliability & Consistency

Quality & Documentation

Architecture Diagrams

High-Level System Architecture

Microservices Architecture

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.

Assign Technician Flow

Assign Technician Flow

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.

Architecture Tests

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.

Domain Layer Protection

Immutability & Encapsulation:

Type Safety:

Application Layer Boundaries

Layer Isolation:

Read/Write Separation:

DTO Boundaries:

Bounded Context Isolation:

Service Boundaries

Benefits

For New Developers:

For Teams:

For Code Reviews:

Evolution from Monolith to Microservices

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.

What Made the Original Template Microservices-Ready

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.

Original Template

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.

Inter-Service Communication

MassTransit Bridge

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.

Shared Event Contracts

The CleanArchitecture.Cmms.Contracts project contains immutable event schemas shared across services:

All services reference this project to ensure consistent message contracts. Events are versioned and immutable once published.

Event Flow

  1. Service publishes integration event → written to outbox table (same transaction)
  2. Outbox processor picks up event → publishes to RabbitMQ via MassTransit
  3. Other services consume from RabbitMQ → invoke integration event handlers
  4. Handlers update local service state

This ensures guaranteed delivery with at-least-once semantics.

Orchestration Service

The Orchestration service manages distributed workflows using the saga pattern. It coordinates operations that span multiple services and handles eventual consistency.

Saga Pattern

Sagas manage long-running transactions across services like:

Eventual Consistency

Operations that require coordination across services are eventually consistent:

Compensation Events

When operations fail, the saga publishes compensation like for example:

This ensures system consistency even when operations fail partway through.

MassTransit State Machines

Sagas are implemented using MassTransit state machines, which provide:

Cross-Service Query Challenge

One challenge in microservices is querying data across service boundaries. Each service has its own database, so traditional joins aren't possible.

The Problem

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.

Key Design Decisions

1. Database-Per-Service

Each service owns its data. This ensures:

2. Event-Driven Communication

Services communicate via events, not direct calls:

3. Outbox Pattern

Integration events written to outbox in same transaction:

4. Saga Orchestration

Complex workflows coordinated by orchestration service:

5. Shared Contracts

Immutable event contracts shared across services:

Architectural Decision Records

The original template's ADRs (ADR-001 through ADR-006) documented foundational patterns that enabled this migration.

New ADRs for microservices:

Quick Start

Prerequisites

Docker Compose

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:

Local Development

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:

Testing Strategy

Unit Tests

Each service has unit tests for:

Run all unit tests:

dotnet test --filter "FullyQualifiedName~UnitTests"

Integration Tests

Integration tests use Testcontainers for:

Run all integration tests:

dotnet test --filter "FullyQualifiedName~IntegrationTests"

Architecture Tests

Automated tests enforce architectural boundaries at compile-time. These tests run as part of the unit test suite and prevent:

What's Not Included

This repository focuses on architecture and design. Cross-cutting concerns that are out of scope:

TODO

Future improvements and enhancements:

Contributing

Contributions welcome. This repository serves as an architecture reference and learning resource.

License

MIT License - see LICENSE file for details.


Built to demonstrate microservices architecture patterns in .NET