CraftersCloud.ReferenceArchitecture.ProjectTemplates
2.1.7-preview.3
See the version list below for details.
dotnet new install CraftersCloud.ReferenceArchitecture.ProjectTemplates::2.1.7-preview.3
Crafters Cloud Reference Architecture
Overview
This project provides a reference for building scalable, high-performing, and maintainable applications using C#, SQL (SQL Server), and Distributed Cache (Redis).
It includes a set of Visual Studio project templates, published as a NuGet package, which can scaffold an entire solution with all the projects pre-configured via dotnet new crafters-starter
, or scaffold a new feature using dotnet new crafters-feature
.
The architecture leverages several modern technologies and best practices to ensure high performance, maintainability, and ease of development. It is based on the principles of Domain-Driven Design (DDD), CQRS, and Vertical Slice Architecture.
Primarily designed to be flexible and extensible, the architecture allows developers to quickly create new features (vertical slices) paired with corresponding integration tests (black-box tests that cover entire verticals). It does not prioritize either Onion Architecture or Clean Architecture, but both can be supported if and when more complex cases arise.
Usage
Prerequisites
- .NET SDK 9
- Aspire SDK
- Docker
Getting Started
Either:
Clone the Git repository
git clone https://github.com/crafters-cloud/crafters-cloud-reference-architecture.git
Or:
Install the Project Templates from NuGet
dotnet new install CraftersCloud.ReferenceArchitecture.ProjectTemplates
Create a New Solution Replace Client.Project and Client Project with the name of your new project.
dotnet new crafters-starter --projectName Client.Project --friendlyName "Client Project" --allow-scripts yes
Run new Solution and Verify
- Start the Docker.
- Open the new solution in Visual Studio or Rider.
- Build the solution and run tests.
Scaffold a New Feature
- Either run the script
scripts\new-feature.ps1
or - execute:
dotnet new crafters-feature --projectName Client.Project --featureName Order --featureNamePlural Orders --allow-scripts yes
- Either run the script
Solution Structure
The solution consists of a set of pre-configured and connected projects, grouped in three solution folders: src, templates, and tests.
src folder
Contains the following projects:
---
title: Project Structure
config:
theme: default
---
flowchart BT
APP[Application]
INFRA[Infrastructure]
API[API]
MIG[Migrations]
MIG_SVC[MigrationService]
SVC_DEF[ServiceDefaults]
APP_HOST[AppHost]
DOMAIN[Domain]
CORE[Core]
DOMAIN --> CORE
APP --> DOMAIN
INFRA --> APP
API --> INFRA
API --> SVC_DEF
MIG --> INFRA
MIG_SVC --> MIG
MIG_SVC --> SVC_DEF
APP_HOST --> API
APP_HOST --> MIG_SVC
- Core: Root project upon which all other projects depend. It should remain lightweight, containing only non-business-related code.
- Domain: Contains the business rules (Domain Entities, Value Objects, Domain Services, Domain Validation rules/invariants).
- Application: Business use case layer where Application Services or Query/Command Handlers are implemented.
- Infrastructure: For "dirty" infrastructure-related code, such as API client implementations, Service Bus integrations, database provider implementations, logging, IoC container bootstrapping, cache implementations, etc.
- Migrations: Contains database migration and seeding definitions.
- MigrationService: A worker project for automatic database migrations, in combination with Aspire Host.
- AppHost: Startup project to initialize dependent resources (e.g., SQL, Redis, API) using Microsoft Aspire.
- Api: "Presentation" layer.
- ServiceDefaults: Aspire Shared project.
templates folder
Contains project templates:
- Feature: Template for adding a new feature.
- Solution: Template for creating a new solution.
tests folder
Contains the following test projects:
- Api.Tests: Tests for the Api project.
- AppHost.Tests: Tests for the AppHost project.
- Domain.Tests: Tests for the Domain layer.
- Infrastructure.Tests: Tests for the Infrastructure layer.
- Tests.Shared: Shared code for test projects.
Domain (and Entities)
All business rules should be defined in the Domain project. Other projects should re-use the business rules located in the Domain as much as possible. These business rules are defined through a set of classes: Entity, Value Object, Domain Events, Validation Rules, Business Rules, and extension methods on the domain classes.
Entity - an object defined primarily by its ID, and not by its properties:
- An Entity is any class that inherits (directly or indirectly) from the
Entity
abstract class. - An Entity class (as defined in this architecture, which knowingly deviates slightly from pure DDD) serves two purposes:
- It is used for mapping between a database table and a class (ORM mapping).
- It encapsulates business rules to ensure domain invariants.
- An Entity class should be designed to prevent invalid domain states (adhering to the Always-Valid Domain Model Approach) and disallow updates without raising domain events (critical for event dispatching). This is achieved by:
- Making all setters private.
- Exposing collections as readonly collections.
- Allowing data updates only through explicitly defined public methods (to ensure that business rules are not violated and domain events are raised).
- Making public methods internally raise domain events (private methods typically do not need to raise them, as they are invoked by public methods).
- Making the default constructor private (used only by EF Core).
- Providing public factory methods for creating entities in a valid state.
- Using the Execute/CanExecute pattern for public methods requiring complex business rule validation:
CanExecute
returns results (consuming code checks the result and only callsExecute
if valid; otherwise, errors are propagated to the calling code).Execute
throws exceptions.
- An Entity is any class that inherits (directly or indirectly) from the
Value Object - an immutable type defined by its properties rather than a unique identity. C# "Records" are ideal for Value Objects. They are mainly used for Entity IDs but can also represent other concepts like names, emails, etc. Value Objects must:
- Be immutable.
- Be comparable based on their properties.
- Always be valid (Value Objects should prevent creation in an invalid state).
- Not have individual identity.
Domain Event - a set of classes that communicate changes occurring in an entity to its event subscribers, following the Publish/Subscribe pattern.
- Domain events are stored in the entity instance using the
AddDomainEvent
method. - Domain events are collected and published after calling
SaveChanges
on the Entity Framework DbContext, but before the database transaction is committed. This allows additional changes to be made as part of the same transaction (e.g., adding integration events to the database).
- Domain events are stored in the entity instance using the
QueryableExtension - a set of classes that extend the
IQueryable<ConcreteEntity>
interface, providing a simple implementation of the Specification pattern.- Instead of creating methods within
IRepository<ConcreteEntity>
, it is more flexible to use extension methods onIQueryable<ConcreteEntity>
so that different queries can be chained or combined.
- Instead of creating methods within
EnumerableExtension - a set of classes that extend the
IEnumerable<ConcreteEntity>
interface.- Filter expressions that cannot be translated by the concrete
Queryable
provider (e.g., Entity Framework) should be placed here. However, if they can be translated, they should belong toQueryableExtensions
instead.
- Filter expressions that cannot be translated by the concrete
Validations - a set of classes that contain validation rules that can be used by either the entity (to validate itself) or by other layers (e.g. API for Request validation, Application for Command/Query validation). With the combination of Always-Valid Domain Model Approach the set of rules should remain simple.
Testing Strategy
Following the principles of Black Box testing, XP (eXtreme Programming), and YAGNI (You Aren't Gonna Need It), the focus is to first write integration tests that cover each feature
(e.g., in the case of an API, testing every API endpoint) and cover a happy-flow path. These tests require minimal setup (no fakes, mocks, or stubs) but only database data seeding.
Later, as the code complexity grows in the domain area (business rules), it is recommended to write unit tests for the domain. With this combination of unit and integration tests, we should achieve a Testing Diamond structure,
where the optimum number of tests is needed to achieve optimum test coverage.
The tests adhere to Black Box testing principles of API endpoints. This approach allows the implementation and architecture of the system to evolve without requiring test updates.
White-box testing (using fakes and mocks) is still possible but is generally considered a code smell for the majority of tests. It can hinder code evolution and architectural changes, as both the implementation and the tests would require updates in tandem.
To verify test results effectively, the libraries VerifyTests and Shouldly are used, providing a more readable way of validating outcomes.
With this strategy, developers are encouraged and eager to write and maintain tests. The refactoring of both the architecture and the code over time ensures technical debt is kept under control.
Arbitrary decisions regarding test coverage (e.g., endpoints, services, domain entities) are avoided, reducing team ambiguity and friction while simplifying pull requests.
Unit tests should be written for complex business rules. However, given that this is just a sample project and there are no complex business rules, it does not include a good example of a unit test.
Key Technologies
- Minimal API: A lightweight, efficient framework for building RESTful services.
- Autofac: A popular IoC container for managing application dependencies.
- Entity Framework: An O/RM enabling database operations via .NET objects.
- MediatR: Simplifies decoupling of requests and handlers.
- Mapperly: A fast object-to-object mapper for transforming objects between layers.
- FluentValidation: Strongly typed model validation rules.
- NUnit: A flexible unit testing framework for .NET.
- TestContainers: An open source library for providing throwaway, lightweight instances of databases, message brokers, web browsers, or just about anything that can run in a Docker container.
- Microsoft Aspire: A set of tools, templates, and packages for building observable, production ready apps.
Together, these technologies create a strong foundation for developing modern, high-quality applications.
How-tos
- Scaffold a new feature.
- Adding property/column to an entity/table
- Builder pattern (TODO).
- Seeding (TODO).
- How to decide when to put logic in the Endpoints or to use Query/Commands (TODO).
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.1.7 | 48 | 2/21/2025 |
2.1.7-preview.4 | 46 | 2/21/2025 |
2.1.7-preview.3 | 39 | 2/21/2025 |
2.1.7-preview.2 | 42 | 2/21/2025 |
2.1.7-preview.1 | 45 | 2/21/2025 |
2.1.6 | 45 | 2/21/2025 |
2.1.5 | 42 | 2/21/2025 |
2.1.4 | 49 | 2/21/2025 |
2.1.3 | 46 | 2/21/2025 |
2.1.2 | 41 | 2/21/2025 |
2.1.1-preview.0.1 | 46 | 2/11/2025 |
2.1.0 | 78 | 2/11/2025 |
2.1.0-preview.2 | 38 | 2/10/2025 |
2.1.0-preview.1 | 35 | 1/22/2025 |
2.1.0-preview.0 | 37 | 1/22/2025 |
2.0.3 | 62 | 2/6/2025 |
2.0.2 | 72 | 1/22/2025 |
2.0.1 | 67 | 1/22/2025 |
2.0.0 | 70 | 1/21/2025 |
2.0.0-preview.6.2 | 32 | 1/21/2025 |
2.0.0-preview.6.1 | 32 | 1/21/2025 |
2.0.0-preview.5 | 32 | 1/17/2025 |
2.0.0-preview.0 | 27 | 1/17/2025 |
1.2.0 | 76 | 1/17/2025 |
1.1.1 | 75 | 1/15/2025 |
1.1.0 | 69 | 1/15/2025 |
0.3.1 | 65 | 1/14/2025 |
0.3.0 | 59 | 1/14/2025 |