BTW.CleanArchitecture.Template
2.1.0
dotnet new install BTW.CleanArchitecture.Template::2.1.0
BTW Clean Architecture Template
Plantilla dotnet new para proyectos .NET 10 con Clean Architecture, PostgreSQL, observabilidad y gestión de secretos opcionales.
Instalación
dotnet new install BTW.CleanArchitecture.Template
Uso
dotnet new clean-arch -n MiProyecto
Parámetros opcionales
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
--loki |
bool |
true |
Integración con Grafana Loki vía BTW.Logging.Loki |
--infisical |
bool |
true |
Gestión de secretos con Infisical vía BTW.SecretsProvider |
--grpc |
bool |
false |
Servicio gRPC con contrato Protobuf |
Los parámetros bool son switches: presencia activa la opción, false explícito la desactiva.
# Sin Infisical ni Loki, solo REST + PostgreSQL
dotnet new clean-arch -n MiProyecto --loki false --infisical false
# Con gRPC habilitado
dotnet new clean-arch -n MiProyecto --grpc
# Todo incluido
dotnet new clean-arch -n MiProyecto --grpc
Prerrequisitos
- .NET 10 SDK
- Docker + Docker Compose (para levantar PostgreSQL, Prometheus, Grafana y opcionalmente Infisical/Loki)
Estructura del proyecto
MiProyecto/
├── Api/ # Capa de presentación
│ ├── Controllers/ # Endpoints REST
│ ├── GrpcServices/ # Servicios gRPC (--grpc)
│ ├── Protos/ # Contratos Protobuf (--grpc)
│ ├── appsettings.json
│ └── Program.cs
├── Application/ # Lógica de negocio
│ ├── DTO/
│ │ ├── Request/ # SampleRequest, PagedRequest
│ │ └── Response/ # SampleResponse, ApiResponse<T>, PagedResult<T>
│ ├── Exceptions/ # AppException, NotFoundException, ConflictException
│ ├── Interfaces/
│ │ ├── Repositories/ # IRepository<T>, ISampleRepository
│ │ ├── Services/ # ISampleService
│ │ ├── UnitOfWork/ # IUnitOfWork
│ │ ├── Metrics/ # ISampleMetrics
│ │ └── Api/ # IApiService
│ ├── Mapping/ # Configuración Mapster
│ └── Services/ # SampleService
├── Domain/ # Entidades y abstracciones
│ └── Entities/
│ ├── Abstractions/ # IAuditableEntity
│ └── Sample.cs
├── Infrastructure/ # Acceso a datos y servicios externos
│ ├── Context/
│ │ ├── Configurations/ # IEntityTypeConfiguration<T>
│ │ └── SampleDbContext.cs
│ ├── Interceptors/ # AuditInterceptor (EF Core)
│ ├── Repositories/ # Repository<T>, SampleRepository
│ ├── UnitOfWork/ # UnitOfWork
│ └── Api/ # ApiService (HTTP client)
├── Host/ # Composición y startup
│ ├── Extensions/ # ServiceCollectionExtensions, GrpcExtensions
│ ├── Metrics/ # SampleMetrics (Prometheus)
│ ├── Middlewares/ # GlobalExceptionHandler
│ └── HostExtensions.cs
├── Shared/ # Utilidades transversales
│ ├── Constants/ # Messages
│ └── Helpers/ # SHA256Helper
├── docker-compose.yml # Base: imagen + healthcheck + red
├── docker-compose.override.yml # Dev local (VS): build + infra completa
├── docker-compose.test.yml # Sobreescritura para ambiente test
├── docker-compose.prod.yml # Sobreescritura para producción
├── deploy.sh # Script despliegue Linux (compose/swarm)
├── deploy.ps1 # Script despliegue Windows (compose/swarm)
├── .env.example # Variables para dev local
├── .env.test.example # Variables para test
└── .env.prod.example # Variables para producción
Stack técnico
| Componente | Tecnología |
|---|---|
| Base de datos | PostgreSQL 17 (Npgsql EF Core) |
| Mapping | Mapster 10 |
| Observabilidad | Prometheus + Grafana |
| Logging | BTW.Logging.Loki → Grafana Loki (opcional) |
| Secretos | BTW.SecretsProvider → Infisical (opcional) |
| API docs | Scalar (/scalar) |
| Health checks | /health (DbContext check incluido) |
| Resiliencia HTTP | Microsoft.Extensions.Http.Resilience + Polly |
Levantar el entorno local
Visual Studio (recomendado)
Abre la solución y selecciona docker-compose como proyecto de inicio. VS carga automáticamente docker-compose.yml + docker-compose.override.yml, levanta PostgreSQL, Prometheus, Grafana (y opcionalmente Infisical/Loki) y hace hot-reload.
CLI
cp .env.example .env
# Editar .env con las credenciales reales
docker compose up -d
Variables de entorno (.env)
APP_DB_PASSWORD=<contraseña para PostgreSQL>
# Solo si --infisical:
INFISICAL_CLIENT_ID=<machine identity client id>
INFISICAL_CLIENT_SECRET=<machine identity client secret>
INFISICAL_PROJECT_ID=<project id>
Secretos gestionados por Infisical (/api)
Cuando --infisical, estos valores deben configurarse en Infisical bajo el path /api:
ConnectionStrings__DefaultConnection = Host=postgres-app;Database=SampleDb;Username=postgres;Password=<APP_DB_PASSWORD>
Loki__Url = http://loki:3100
Primera vez con Infisical self-hosted:
docker compose up -d— levanta todos los servicios- Abrir
http://localhost:8888→ crear cuenta y proyecto - Crear Machine Identity y copiar sus credenciales al
.env - Agregar los secretos indicados bajo el path
/api
Despliegue en servidor
Usa los scripts de despliegue. Soportan Docker Compose (servidor único) y Docker Swarm (múltiples réplicas).
# Linux — test con Docker Compose
./deploy.sh test compose
# Linux — producción con Docker Swarm
./deploy.sh prod swarm
# Windows — test con Docker Compose
.\deploy.ps1 -Env test -Mode compose
# Windows — producción con Docker Swarm
.\deploy.ps1 -Env prod -Mode swarm
Los scripts copian automáticamente la configuración correcta de Prometheus (static_configs para compose, dns_sd_configs para swarm). Requieren .env.test o .env.prod (copiar desde los .example).
Patrones de arquitectura
Repository + Unit of Work
IUnitOfWork expone los repositorios tipados y centraliza SaveChangesAsync y la gestión de transacciones:
public interface IUnitOfWork : IAsyncDisposable
{
ISampleRepository SampleRepository { get; }
Task<int> SaveChangesAsync(CancellationToken ct = default);
Task BeginTransactionAsync(CancellationToken ct = default);
Task CommitTransactionAsync(CancellationToken ct = default); // NO llama SaveChanges internamente
Task RollbackTransactionAsync(CancellationToken ct = default);
}
Los servicios inyectan solo IUnitOfWork y acceden al repositorio a través de él. EF Core garantiza que el DbContext sea la misma instancia (Scoped).
Paginación
// Request
GET /sample?page=1&pageSize=10
// Response
{
"success": true,
"result": {
"items": [...],
"totalCount": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10,
"hasPreviousPage": false,
"hasNextPage": true
}
}
Jerarquía de excepciones
AppException (abstract)
├── NotFoundException → 404
├── ConflictException → 409
└── (extensible) → 400
Los servicios lanzan excepciones de dominio. GlobalExceptionHandler las mapea a códigos HTTP sin que la capa de aplicación conozca HTTP.
Auditoría automática
Las entidades que implementan IAuditableEntity reciben CreatedAt y UpdatedAt automáticamente vía AuditInterceptor (EF Core SaveChangesInterceptor).
ApiResponse
Los servicios retornan objetos de dominio. Los controllers los envuelven:
// Servicio
Task<SampleResponse> GetSampleByIdAsync(int id, CancellationToken ct);
// Controller
SampleResponse result = await sampleService.GetSampleByIdAsync(id, ct);
return Ok(ApiResponse<SampleResponse>.Ok(result));
Migraciones EF Core
cd Api
dotnet ef migrations add InitialCreate --project ../Infrastructure
dotnet ef database update
Endpoints disponibles
| Método | Ruta | Descripción |
|---|---|---|
| GET | /sample |
Lista paginada (?page=1&pageSize=10) |
| GET | /sample/{id} |
Obtener por ID |
| POST | /sample |
Crear nuevo |
| GET | /health |
Health check (liveness + DB) |
| GET | /metrics |
Métricas Prometheus |
| GET | /scalar |
Documentación interactiva |
Observabilidad
- Prometheus:
http://localhost:9090 - Grafana:
http://localhost:3000(admin/admin) - Loki (si habilitado):
http://localhost:3100 - Métricas expuestas:
samples_created_total,samples_get_all_duration_seconds
Licencia
MIT
-
net10.0
- 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.