16 KiB
16 KiB
.NET 10 Microservice Template
Enterprise-grade .NET 10 microservice template following DDD, CQRS, and Clean Architecture patterns.
Overview
This template provides a production-ready structure for .NET microservices based on the eShopOnContainers reference architecture with:
- Domain-Driven Design (DDD) - Aggregates, Entities, Value Objects, Domain Events
- CQRS Pattern - Separate Commands (write) and Queries (read) with MediatR
- Clean Architecture - Domain, Infrastructure, API layered separation
- EF Core 10 - PostgreSQL with connection resilience
- FluentValidation - Request validation
- API Versioning - URL segment versioning
- Health Checks - Kubernetes-ready probes
- Structured Logging - Serilog with console and Seq
Prerequisites
| Requirement | Version |
|---|---|
| .NET SDK | 10.0.101+ |
| Docker | 24.0+ |
| PostgreSQL | 15+ (or use Docker) |
Quick Start
1. Create New Service
# Copy template to new service
cp -r services/_template_dot_net services/your-service-name-net
cd services/your-service-name-net
# Rename all occurrences of "MyService" to "YourService"
# Example: MyService → StorageService, UserService, PaymentService
find . -type f -name "*.cs" -exec sed -i '' 's/MyService/YourService/g' {} +
find . -type f -name "*.csproj" -exec sed -i '' 's/MyService/YourService/g' {} +
find . -type f -name "*.sln" -exec sed -i '' 's/MyService/YourService/g' {} +
Naming Convention:
- Service folder:
{service-name}-net(e.g.,storage-service-net,iam-service-net) - Projects:
{ServiceName}.API,{ServiceName}.Domain,{ServiceName}.Infrastructure - DbContext:
{ServiceName}Context(e.g.,StorageServiceContext) - Namespace:
GoodGo.Services.{ServiceName}(e.g.,GoodGo.Services.StorageService)
2. Configure Environment
cp .env.example .env
nano .env
3. Run with Docker
docker-compose up -d
docker-compose logs -f myservice-api
4. Run Locally
dotnet restore
dotnet build
dotnet run --project src/MyService.API
Project Structure
{service-name}-net/ # Example: storage-service-net
├── src/
│ ├── {ServiceName}.API/ # Presentation Layer (Controllers, CQRS)
│ │ ├── Controllers/ # API endpoints
│ │ ├── Application/ # CQRS Implementation
│ │ │ ├── Commands/ # Write operations (MediatR)
│ │ │ ├── Queries/ # Read operations
│ │ │ ├── Behaviors/ # MediatR pipeline behaviors
│ │ │ └── Validations/ # FluentValidation validators
│ │ ├── Middlewares/ # Custom middlewares
│ │ └── Program.cs # Application entry point
│ │
│ ├── {ServiceName}.Domain/ # Domain Layer (Pure business logic)
│ │ ├── AggregatesModel/ # Aggregate roots and entities
│ │ ├── Events/ # Domain events
│ │ ├── Exceptions/ # Domain exceptions
│ │ └── SeedWork/ # Base classes (Entity, IAggregateRoot)
│ │
│ └── {ServiceName}.Infrastructure/ # Infrastructure Layer
│ ├── EntityConfigurations/ # EF Core configurations (Fluent API)
│ ├── Repositories/ # Repository implementations
│ ├── Providers/ # External service providers (if any)
│ ├── Services/ # Infrastructure services
│ ├── Migrations/ # EF Core migrations
│ └── {ServiceName}Context.cs # DbContext with Unit of Work
│
├── tests/
│ ├── {ServiceName}.UnitTests/ # Unit tests
│ └── {ServiceName}.FunctionalTests/ # Integration tests
│
├── docs/
│ ├── en/ # English documentation
│ │ ├── README.md
│ │ └── ARCHITECTURE.md
│ └── vi/ # Vietnamese documentation
│ ├── README.md
│ └── ARCHITECTURE.md
│
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Local development setup
└── README.md # Service overview (bilingual)
Real-World Examples:
- Storage Service:
services/storage-service-net/ - IAM Service:
services/iam-service-net/ - Chat Service:
services/chat-service-net/
Architecture
graph TB
subgraph "API Layer"
C[Controllers]
CMD[Commands]
Q[Queries]
B[Behaviors]
V[Validations]
end
subgraph "Domain Layer"
AR[Aggregate Roots]
E[Entities]
VO[Value Objects]
DE[Domain Events]
end
subgraph "Infrastructure Layer"
DB[(PostgreSQL)]
R[Repositories]
CTX[DbContext]
end
C --> CMD
C --> Q
CMD --> B --> V
CMD --> AR
Q --> R
R --> CTX --> DB
style C fill:#4a90d9,stroke:#2d5986,color:#fff
style AR fill:#50c878,stroke:#2d8659,color:#fff
style DB fill:#ff6b6b,stroke:#c0392b,color:#fff
Layer Responsibilities
1. Domain Layer
- ZERO external dependencies (except MediatR.Contracts)
- Contains only POCO classes
- Implements DDD tactical patterns
| Component | Purpose |
|---|---|
| SeedWork | Base classes: Entity, ValueObject, IAggregateRoot |
| AggregatesModel | Aggregate roots with entities and value objects |
| Events | Domain events for cross-aggregate communication |
| Exceptions | Domain-specific exceptions |
2. Infrastructure Layer
- Database access (EF Core)
- Repository implementations
- External service integrations
3. API Layer
- Controllers for HTTP handling
- Commands for write operations
- Queries for read operations
- MediatR behaviors for cross-cutting concerns
CQRS Pattern
Commands (Write Operations)
public record CreateSampleCommand(string Name, string? Description)
: IRequest<CreateSampleCommandResult>;
public class CreateSampleCommandHandler : IRequestHandler<CreateSampleCommand, CreateSampleCommandResult>
{
public async Task<CreateSampleCommandResult> Handle(CreateSampleCommand request, CancellationToken ct)
{
var sample = new Sample(request.Name, request.Description);
_repository.Add(sample);
await _repository.UnitOfWork.SaveEntitiesAsync(ct);
return new CreateSampleCommandResult(sample.Id);
}
}
MediatR Pipeline
Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/samples |
Get all samples |
GET |
/api/v1/samples/{id} |
Get sample by ID |
POST |
/api/v1/samples |
Create new sample |
PUT |
/api/v1/samples/{id} |
Update sample |
DELETE |
/api/v1/samples/{id} |
Delete sample |
PATCH |
/api/v1/samples/{id}/status |
Change status |
Health Endpoints
| Endpoint | Purpose |
|---|---|
/health |
Full health status |
/health/live |
Liveness probe |
/health/ready |
Readiness probe |
Environment Variables
Required
| Variable | Description | Default |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Environment name | Development |
DATABASE_URL |
Neon PostgreSQL connection | - |
ConnectionStrings__DefaultConnection |
Standard connection string format | - |
Optional
| Variable | Description | Default |
|---|---|---|
REDIS_URL |
Redis connection (cache) | - |
RabbitMQ__Host |
RabbitMQ hostname | localhost |
RabbitMQ__Port |
RabbitMQ port | 5672 |
RabbitMQ__Username |
RabbitMQ username | guest |
RabbitMQ__Password |
RabbitMQ password | guest |
Inter-Service Communication
| Variable | Description | Default |
|---|---|---|
IamService__BaseUrl |
IAM Service URL | http://iam-service-net:5001 |
Services__InternalApiKey |
Shared secret for service-to-service auth | - |
Multi-Provider Pattern (Example: Storage Service)
| Variable | Description | Default |
|---|---|---|
Storage__Provider |
Provider: minio or aliyun |
minio |
Storage__DefaultBucket |
Default bucket name | storage |
Storage__MinIO__Endpoint |
MinIO endpoint | localhost:9000 |
Storage__MinIO__AccessKey |
MinIO access key | - |
Storage__MinIO__SecretKey |
MinIO secret key | - |
Storage__AliyunOSS__Endpoint |
Aliyun OSS endpoint | - |
Storage__AliyunOSS__AccessKeyId |
Aliyun access key | - |
Storage__AliyunOSS__AccessKeySecret |
Aliyun secret key | - |
Testing
# Run all tests
dotnet test
# Run with coverage
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
# Run specific test project
dotnet test tests/MyService.UnitTests
Deployment
Docker Build
docker build -t myservice:latest .
docker run -p 5000:8080 --env-file .env myservice:latest
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice-api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: myservice:latest
livenessProbe:
httpGet:
path: /health/live
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
Advanced Patterns
Multi-Provider Pattern
Use when: Service needs to support multiple external providers (storage, payment, SMS)
Example: Storage Service with MinIO and Aliyun OSS
// Domain - Interface
public interface IStorageProvider
{
Task<string> UploadFileAsync(Stream fileStream, string fileName, CancellationToken ct);
Task<Stream> DownloadFileAsync(string objectKey, CancellationToken ct);
Task DeleteFileAsync(string objectKey, CancellationToken ct);
Task<string> GeneratePresignedUrlAsync(string objectKey, int expiryMinutes, CancellationToken ct);
}
// Infrastructure - MinIO Implementation
public class MinioStorageProvider : IStorageProvider
{
private readonly MinioClient _client;
// Implementation...
}
// Infrastructure - Aliyun OSS Implementation
public class AliyunOssStorageProvider : IStorageProvider
{
private readonly OssClient _client;
// Implementation...
}
// API - Provider Factory
public class StorageProviderFactory
{
public IStorageProvider CreateProvider(IConfiguration config)
{
var provider = config["Storage:Provider"];
return provider?.ToLower() switch
{
"minio" => new MinioStorageProvider(config),
"aliyun" => new AliyunOssStorageProvider(config),
_ => throw new InvalidOperationException($"Unknown provider: {provider}")
};
}
}
File Reference: StorageService.Infrastructure/Providers/
Inter-Service Authentication Pattern
Use when: Service needs to validate JWT tokens with IAM Service
// Infrastructure - IAM Service Client
public class IamServiceClient
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _config;
public IamServiceClient(HttpClient httpClient, IConfiguration config)
{
_httpClient = httpClient;
_config = config;
_httpClient.BaseAddress = new Uri(_config["IamService:BaseUrl"]);
}
public async Task<UserInfo> ValidateTokenAsync(string token, CancellationToken ct)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/auth/validate")
{
Headers = { { "Authorization", $"Bearer {token}" } }
};
var response = await _httpClient.SendAsync(request, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<UserInfo>(ct);
}
}
// API - Middleware
public class JwtValidationMiddleware
{
private readonly RequestDelegate _next;
private readonly IamServiceClient _iamClient;
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (!string.IsNullOrEmpty(token))
{
var userInfo = await _iamClient.ValidateTokenAsync(token, context.RequestAborted);
context.Items["UserId"] = userInfo.UserId;
}
await _next(context);
}
}
What's New in .NET 10
- C# 14 language features
- Improved Native AOT support
- Better async/await performance
- Enhanced JSON serialization (System.Text.Json)
- 3-year LTS support (until November 2028)
- Entity Framework Core 10 with performance improvements
Platform Integration
Traefik Routing
Add labels to docker-compose.yml:
services:
your-service-net:
build:
context: ../..
dockerfile: services/your-service-net/Dockerfile
labels:
- "traefik.enable=true"
- "traefik.http.routers.your-service.rule=PathPrefix(`/api/v1/your-service`)"
- "traefik.http.services.your-service.loadbalancer.server.port=8080"
- "traefik.http.routers.your-service.middlewares=strip-prefix@docker"
Neon PostgreSQL Configuration
{
"ConnectionStrings": {
"DefaultConnection": "Host=ep-xxx.neon.tech;Database=your_service;Username=xxx;Password=xxx;SSL Mode=Require;Trust Server Certificate=true"
}
}
Best Practices:
- Use connection pooling:
Pooling=true;Maximum Pool Size=100 - Enable SSL:
SSL Mode=Require - Set command timeout:
Command Timeout=30
RabbitMQ Integration (instead of Kafka)
// Domain Event
public record FileUploadedEvent(string FileId, string UserId, long FileSize) : IDomainEvent;
// Infrastructure - Event Publisher
public class RabbitMqEventPublisher : IEventPublisher
{
private readonly IConnection _connection;
private readonly IModel _channel;
public async Task PublishAsync<T>(T @event, CancellationToken ct) where T : IDomainEvent
{
var message = JsonSerializer.Serialize(@event);
var body = Encoding.UTF8.GetBytes(message);
_channel.BasicPublish(
exchange: "domain_events",
routingKey: typeof(T).Name,
body: body
);
}
}
Related Documentation
Architecture
- System Design - Overall GoodGo Platform architecture
- Microservices Communication - Communication patterns
- Caching Architecture - Redis caching strategies
Skills
- CQRS with MediatR - CQRS pattern implementation
- Repository Pattern - EF Core repository pattern
- Domain-Driven Design - DDD tactical patterns
- Error Handling - Exception handling patterns
Guides
- Local Development - Setup dev environment
- Deployment - Deploy to Kubernetes
Additional Resources
Microsoft Official
Architecture References
- eShopOnContainers - Microservices reference architecture
- .NET Microservices Architecture Book
Libraries
- MediatR - CQRS implementation
- FluentValidation - Validation library
- Polly - Resilience and transient-fault-handling
Tools
- EF Core Tools - Migrations and scaffolding
- Neon PostgreSQL - Serverless Postgres documentation
Last Updated: 2026-01-14
Template Version: 2.0
Maintained By: GoodGo Architecture Team