This repository is an open-source reference implementation of ASP.NET Core using Clean Architecture, CQRS, and Event Sourcing principles. It is designed to demonstrate how to structure a production-ready solution around separation of concerns, testability, and maintainability.
If you found this project useful, please consider giving it a star. Thank you!
- ASP.NET Core 10
- Entity Framework Core 10
- EF Compiled Queries
- xUnit, FluentAssertions, and NSubstitute for automated testing
- Polly for resilience policies
- AutoMapper for object mapping
- FluentValidation for input validation
- MediatR for in-process messaging
- Ardalis.Result for result-based error handling
- OpenAPI and Scalar for API documentation
- Health Checks
- Microsoft SQL Server, MongoDB, and Redis
- Docker and Docker Compose
- Distroless .NET images
The solution is organized around a layered, domain-first design that emphasizes:
- Clear separation of responsibilities across application, domain, infrastructure, and presentation layers
- SOLID principles and clean code practices
- Domain-driven design with entities, value objects, and aggregates
- Domain events and notifications
- CQRS for separating command and query responsibilities
- Event Sourcing for persisting state transitions
- Repository and Unit of Work patterns
- Result-based handling for predictable application outcomes
Before running the application locally, make sure you have:
- .NET SDK 10
- Docker Desktop (or Docker Engine with Compose support)
- A terminal with access to the repository root
- Clone the repository and navigate to the project root.
- Restore and build the solution:
dotnet clean Shop.slnx --nologo /tl && dotnet build Shop.slnx --nologo /tl- Create a
.envfile with the required environment variables:
MSSQL_SA_PASSWORD=YOUR_STRONG_!Passw0rd
REDIS_PASSWORD=YOUR_STRONG_!Passw0rd
MSSQL_PORT=1433
MONGO_PORT=27017
REDIS_PORT=6379
ASPNETCORE_ENVIRONMENT=Development- Start the infrastructure and application containers:
docker compose up --build- Open the API documentation in your browser:
http://localhost:{port}/scalar/v1
This repository uses a layered testing strategy to keep the solution reliable while preserving the principles of Clean Architecture and CQRS.
- Unit tests are located in tests/Shop.UnitTests and focus on domain entities, value objects, validators, handlers, queries, and mapping logic.
- Integration tests are located in tests/Shop.IntegrationTests and cover API and infrastructure behavior.
The unit test project in tests/Shop.UnitTests/Shop.UnitTests.csproj uses:
- xUnit for test execution
- FluentAssertions for readable assertions
- NSubstitute for dependency mocking
- Coverlet for coverage reporting
- Microsoft.EntityFrameworkCore.Sqlite for database-backed tests
From the repository root, run:
dotnet test tests/Shop.UnitTests/Shop.UnitTests.csprojTo run the full solution test suite:
dotnet test Shop.slnxTo run a subset of tests by name:
dotnet test tests/Shop.UnitTests/Shop.UnitTests.csproj --filter "FullyQualifiedName~CreateCustomer"A typical unit test follows the xUnit and FluentAssertions pattern used throughout the project:
public class CreateCustomerCommandValidatorTests
{
[Fact]
public void Validate_Should_Fail_When_EmailIsInvalid()
{
var validator = new CreateCustomerCommandValidator();
var command = new CreateCustomerCommand("Jane Doe", "invalid-email");
var result = validator.Validate(command);
result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle(e => e.PropertyName == "Email");
}
}- Keep unit tests fast and isolated; prefer deterministic inputs and small collaborators over full infrastructure dependencies.
- Test command, query, validator, and domain aggregate behavior directly rather than relying on implementation details.
- For CQRS, verify that handlers and validators produce the expected results and side effects.
- For Event Sourcing, assert that domain events are emitted correctly and that aggregates can be reconstructed from historical events.
- Use descriptive test names so failures clearly communicate what behavior broke.
To inspect performance traces while the application is running, open:
http://localhost:{port}/profiler/results-index
