A production-ready Clean Architecture template for .NET 10 applications, featuring Domain-Driven Design (DDD), CQRS pattern, and comprehensive testing practices.
- Clean Architecture - Layered architecture with clear separation of concerns
- Domain-Driven Design - Entities, Value Objects, Aggregate Roots, Domain Events
- CQRS Pattern - Command Query Responsibility Segregation with Mediator
- Multiple Database Support - SQLite, PostgreSQL, MongoDB
- JWT Authentication - Role-based and permission-based authorization
- NATS Messaging - Event-driven architecture with request-reply and pub-sub patterns
- API Versioning - Built-in API versioning support
- Swagger/OpenAPI - Auto-generated API documentation
- Comprehensive Testing - Unit, integration, and subcutaneous tests
- Distributed Cache - NATS KV-based distributed caching with IDistributedCache
- Object Store - Binary file storage with NATS Object Store
- SAGA Pattern - Distributed transaction orchestration with compensation
- Observability - OpenTelemetry tracing and metrics
- Swagger-like UI for NATS endpoints with direct interaction support and copy-paste ready payloads

- Direct operation support

- Automatically converts articles from
docs/wiki/{en,zh}to static web pages - Supports markdown rendering

WedaTemplate/
├── src/
│ ├── Weda.Core/ # Shared infrastructure (DDD, CQRS, NATS, Cache, SAGA)
│ ├── Weda.Template.Api/ # REST API layer
│ ├── Weda.Template.Application/ # Application/CQRS layer
│ ├── Weda.Template.Contracts/ # DTOs and contracts
│ ├── Weda.Template.Domain/ # Domain layer
│ └── Weda.Template.Infrastructure/ # Infrastructure layer
├── tests/
│ ├── Weda.Template.Api.IntegrationTests/
│ ├── Weda.Template.Application.UnitTests/
│ ├── Weda.Template.Domain.UnitTests/
│ ├── Weda.Template.Infrastructure.UnitTests/
│ └── Weda.Template.TestCommon/
└── tools/
└── WikiGenerator/
- .NET 10 SDK
- Docker & Docker Compose (optional)
- NATS Server (optional, for messaging features)
dotnet run --project src/Weda.Template.Apidocker compose upThe API will be available at http://localhost:5001
Navigate to http://localhost:5001/swagger to explore the API documentation.
- Hierarchical organization structure with supervisor relationships
- Department management (Engineering, HR, Finance, Marketing, Sales, Operations)
- Status tracking (Active, OnLeave, Inactive)
- Subordinate management with circular reference prevention
- Email-based authentication
- Role management (User, Admin, SuperAdmin)
- Permission-based access control
- Login tracking
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/login |
User login |
| Method | Endpoint | Role | Description |
|---|---|---|---|
| GET | /api/v1/users/me |
Authenticated | Get current user |
| GET | /api/v1/users |
Admin | List all users |
| GET | /api/v1/users/{id} |
Admin | Get user by ID |
| POST | /api/v1/users |
Admin | Create user |
| PUT | /api/v1/users/{id} |
Admin | Update user |
| PUT | /api/v1/users/{id}/roles |
SuperAdmin | Update roles |
| DELETE | /api/v1/users/{id} |
Admin | Delete user |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/employees |
List all employees |
| GET | /api/v1/employees/{id} |
Get employee by ID |
| POST | /api/v1/employees |
Create employee |
| PUT | /api/v1/employees/{id} |
Update employee |
| DELETE | /api/v1/employees/{id} |
Delete employee |
| GET | /api/v1/employees/{id}/subordinates |
Get subordinates |
The template provides EventController, an abstraction similar to ASP.NET Core's ApiController but for NATS messaging. This allows you to handle NATS messages with familiar patterns.
[ApiVersion("1")]
public class EmployeeEventController : EventController
{
// Request-Reply pattern with subject-based routing
[Subject("[controller].v{version:apiVersion}.{id}.get")]
public async Task<GetEmployeeResponse> GetEmployee(int id)
{
var query = new GetEmployeeQuery(id);
var result = await Mediator.Send(query);
return new GetEmployeeResponse(result.Value);
}
// Create employee
[Subject("[controller].v{version:apiVersion}.create")]
public async Task<GetEmployeeResponse> CreateEmployee(CreateEmployeeRequest request)
{
var command = new CreateEmployeeCommand(request.Name, request.Email, ...);
var result = await Mediator.Send(command);
return new GetEmployeeResponse(result.Value);
}
// JetStream Consume pattern (fire-and-forget)
[Subject("[controller].v{version:apiVersion}.created")]
public async Task OnEmployeeCreated(CreateEmployeeNatsEvent @event)
{
var command = new CreateEmployeeCommand(@event.Name, @event.Email, ...);
await Mediator.Send(command);
}
}| Pattern | Description | Use Case |
|---|---|---|
| Request-Reply | Synchronous request with response | CRUD operations |
| JetStream Consume | Continuous message processing | Event handlers |
| JetStream Fetch | Batch message processing | Bulk operations |
| Core Pub-Sub | Fire-and-forget messaging | Notifications |
| Subject | Description |
|---|---|
employee.v1.{id}.get |
Get employee by ID |
employee.v1.getAll |
List all employees |
employee.v1.create |
Create employee |
employee.v1.{id}.update |
Update employee |
employee.v1.{id}.delete |
Delete employee |
Copy and paste these commands to interact with the NATS endpoints directly:
Get Employee by ID:
nats req employee.v1.1.get ''List All Employees:
nats req employee.v1.getAll ''Create Employee:
nats req employee.v1.create '{"name":"John Doe","email":"john@example.com","department":"Engineering","position":"Software Engineer"}'Update Employee:
nats req employee.v1.1.update '{"name":"John Doe","email":"john.doe@example.com","department":"Engineering","position":"Senior Engineer","status":"Active"}'Delete Employee:
nats req employee.v1.1.delete ''{
"Database": {
"Provider": "Sqlite",
"ConnectionString": "Data Source=Weda.Template.sqlite"
}
}Supported providers: Sqlite, PostgreSQL, MongoDB
{
"JwtSettings": {
"Secret": "your-secret-key-at-least-32-characters",
"TokenExpirationInMinutes": 60,
"Issuer": "WedaTemplate",
"Audience": "WedaTemplate"
}
}{
"Nats": {
"Url": "nats://localhost:4222",
"Name": "weda-template"
}
}{
"EmailSettings": {
"EnableEmailNotifications": false,
"DefaultFromEmail": "your-email@example.com",
"SmtpSettings": {
"Server": "smtp.gmail.com",
"Port": 587,
"Username": "your-email@gmail.com",
"Password": "your-password"
}
}
}The template supports three types of authorization:
[Authorize(Roles = "Admin")]
public record GetUserQuery(Guid Id) : IAuthorizeableRequest<ErrorOr<User>>;[Authorize(Permissions = "users:read")]
public record ListUsersQuery : IAuthorizeableRequest<ErrorOr<List<User>>>;[Authorize(Policies = "SelfOrAdmin")]
public record UpdateUserCommand(Guid Id, ...) : IAuthorizeableRequest<ErrorOr<User>>;# Run all tests
dotnet test
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"- Domain Unit Tests - Test domain entities and value objects
- Application Unit Tests - Test handlers and pipeline behaviors
- Infrastructure Unit Tests - Test repositories and persistence
- Integration Tests - End-to-end API testing
| Pattern | Implementation |
|---|---|
| Domain-Driven Design | Entity, AggregateRoot, Value Objects, Domain Events |
| CQRS | Mediator-based command/query separation |
| Repository Pattern | Generic and specialized repositories |
| Pipeline Behaviors | Validation and authorization cross-cutting |
| Eventual Consistency | Middleware-based domain event publishing |
| Event-Driven | NATS messaging for async communication |
| Distributed Cache | NATS KV with IDistributedCache interface |
| Object Store | NATS Object Store for binary files |
| SAGA Pattern | Orchestration-based distributed transactions |
| Observability | OpenTelemetry tracing and metrics |
The Weda.Core library provides production-ready infrastructure patterns:
// Inject IDistributedCache
public class MyService(IDistributedCache cache)
{
public async Task CacheDataAsync(string key, MyData data)
{
var json = JsonSerializer.Serialize(data);
await cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
}
}// Inject IBlobStorage
public class FileService(IBlobStorage storage)
{
public async Task<string> UploadAsync(Stream file, string filename)
{
return await storage.UploadAsync(file, filename);
}
public async Task<Stream> DownloadAsync(string filename)
{
return await storage.DownloadAsync(filename);
}
}// Define saga steps
public class CreateOrderStep : ISagaStep<OrderSagaData>
{
public string Name => "CreateOrder";
public async Task<ErrorOr<OrderSagaData>> ExecuteAsync(OrderSagaData data, CancellationToken ct)
{
// Create order logic
return data;
}
public async Task<ErrorOr<OrderSagaData>> CompensateAsync(OrderSagaData data, CancellationToken ct)
{
// Rollback order creation
return data;
}
}
// Execute saga
var result = await sagaOrchestrator.ExecuteAsync(saga, initialData);{
"Observability": {
"ServiceName": "MyService",
"Tracing": {
"Enabled": true,
"UseConsoleExporter": true,
"OtlpEndpoint": "http://localhost:4317"
},
"Metrics": {
"Enabled": true,
"UseConsoleExporter": false
}
}
}This project is licensed under the MIT License.

