🏗️ Ejemplo Práctico: Arquitectura Limpia en VetCarePro
🎯 El Dominio - El Corazón del Sistema
El Dominio es donde viven las reglas de negocio más importantes. Es como el "cerebro" de la aplicación veterinaria:
// �� VETCAREPRO DOMAIN - Las reglas de negocio
// 1. ENTIDADES (Los objetos principales del negocio)
public class Mascota : BaseEntity
{
public string Nombre { get; private set; }
public TipoMascota Tipo { get; private set; }
public decimal Peso { get; private set; }
public DateTime FechaNacimiento { get; private set; }
// 📋 REGLAS DE NEGOCIO - Aquí está la lógica importante
public int EdadEnAnios => DateTime.Today.Year - FechaNacimiento.Year;
public bool PuedeTenerVacuna()
{
// 🧠 REGLA: Solo mascotas mayores de 2 meses pueden vacunarse
var edadEnMeses = (DateTime.Today - FechaNacimiento).Days / 30;
return edadEnMeses >= 2;
}
public void ActualizarPeso(decimal nuevoPeso)
{
// 🧠 REGLA: El peso debe ser positivo y menor a 500kg
if (nuevoPeso <= 0 || nuevoPeso > 500)
throw new ArgumentException("Peso inválido");
Peso = nuevoPeso;
}
}
// 2. VALUE OBJECTS (Conceptos del negocio)
public class Dinero
{
public decimal Monto { get; private set; }
public string Moneda { get; private set; }
public Dinero(decimal monto, string moneda = "PEN")
{
if (monto < 0) throw new ArgumentException("El dinero no puede ser negativo");
Monto = monto;
Moneda = moneda;
}
// 🧠 REGLA: Cálculo de descuentos
public Dinero AplicarDescuento(decimal porcentaje)
{
var descuento = Monto * (porcentaje / 100);
return new Dinero(Monto - descuento, Moneda);
}
}
// 3. SERVICIOS DE DOMINIO (Lógica compleja de negocio)
public class CalculadoraPreciosVenta
{
public Dinero CalcularPrecioFinal(Dinero precioBase, decimal ivaPorcentaje = 19)
{
// 🧠 REGLA: Aplicar IVA del 19% (Perú)
var iva = precioBase.Monto * (ivaPorcentaje / 100);
return new Dinero(precioBase.Monto + iva, precioBase.Moneda);
}
}
�� Puntos Clave del Dominio:
- ✅ No depende de nada externo (ni bases de datos, ni frameworks)
- ✅ Contiene las reglas de negocio más importantes
- ✅ Es puro C# - sin dependencias de .NET específicas
- ✅ Fácil de testear - sin configuraciones externas
⚡ Capa de Aplicación - El Orquestador
La Aplicación coordina las operaciones y conecta el dominio con el mundo exterior:
// 🚀 VETCAREPRO APPLICATION - Coordinación y orquestación
// 1. COMMANDS (Operaciones que cambian el estado)
public record CreateMascotaCommand(
string Nombre,
TipoMascota Tipo,
string Raza,
SexoMascota Sexo,
DateTime FechaNacimiento,
decimal Peso,
string Color,
Guid PropietarioId,
string? Observaciones
);
// 2. HANDLERS (Ejecutan las operaciones)
public class CreateMascotaHandler : IRequestHandler<CreateMascotaCommand, Result<Guid>>
{
private readonly IMascotaRepository _mascotaRepository;
private readonly IPropietarioRepository _propietarioRepository;
public async Task<Result<Guid>> Handle(CreateMascotaCommand request, CancellationToken cancellationToken)
{
// 🔍 1. VALIDAR (usar el dominio)
var mascota = Mascota.Create(
request.Nombre,
request.Tipo,
request.Raza,
request.Sexo,
request.FechaNacimiento,
request.Peso,
request.Color,
request.Observaciones
);
// 🔍 2. VERIFICAR REGLAS DE NEGOCIO
var propietario = await _propietarioRepository.GetByIdAsync(request.PropietarioId);
if (propietario == null)
return Result.Failure<Guid>("Propietario no encontrado");
// 💾 3. GUARDAR (usar infraestructura)
await _mascotaRepository.AddAsync(mascota);
return Result.Success(mascota.Id);
}
}
// 3. QUERIES (Operaciones de consulta)
public record GetMascotasByPropietarioQuery(Guid PropietarioId);
public class GetMascotasByPropietarioHandler : IRequestHandler<GetMascotasByPropietarioQuery, List<MascotaDto>>
{
private readonly IMascotaRepository _mascotaRepository;
private readonly IMapper _mapper;
public async Task<List<MascotaDto>> Handle(GetMascotasByPropietarioQuery request, CancellationToken cancellationToken)
{
var mascotas = await _mascotaRepository.GetByPropietarioIdAsync(request.PropietarioId);
return _mapper.Map<List<MascotaDto>>(mascotas);
}
}
🔑 Puntos Clave de la Aplicación:
- ✅ Coordina entre dominio e infraestructura
- ✅ No contiene lógica de negocio (esa va en el dominio)
- ✅ Maneja validaciones, mapeos y orquestación
- ✅ Usa MediatR para desacoplar comandos y queries
🗄️ Capa de Infraestructura - El Almacén
La Infraestructura se encarga de persistir datos y conectar con sistemas externos:
// �� VETCAREPRO INFRASTRUCTURE - Persistencia y sistemas externos
// 1. REPOSITORIOS (Implementan las interfaces del dominio)
public class MascotaRepository : IMascotaRepository
{
private readonly ApplicationDbContext _context;
public async Task<Mascota?> GetByIdAsync(Guid id)
{
// 🗄️ CONVERTIR de Entidad EF a Entidad de Dominio
var mascotaEntity = await _context.Mascotas
.Include(m => m.Propietario)
.FirstOrDefaultAsync(m => m.Id == id);
if (mascotaEntity == null) return null;
// 🔄 MAPEO: EF Entity → Domain Entity
return Mascota.Create(
mascotaEntity.Nombre,
mascotaEntity.Tipo,
mascotaEntity.Raza,
mascotaEntity.Sexo,
mascotaEntity.FechaNacimiento,
mascotaEntity.Peso,
mascotaEntity.Color,
mascotaEntity.Observaciones
);
}
public async Task AddAsync(Mascota mascota)
{
// �� MAPEO: Domain Entity → EF Entity
var mascotaEntity = new MascotaEntity
{
Id = mascota.Id,
Nombre = mascota.Nombre,
Tipo = mascota.Tipo,
// ... otros campos
FechaCreacion = DateTime.UtcNow
};
_context.Mascotas.Add(mascotaEntity);
await _context.SaveChangesAsync();
}
}
// 2. CONFIGURACIONES DE ENTITY FRAMEWORK
public class MascotaConfiguration : IEntityTypeConfiguration<MascotaEntity>
{
public void Configure(EntityTypeBuilder<MascotaEntity> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Nombre).IsRequired().HasMaxLength(100);
builder.Property(m => m.Peso).HasPrecision(5, 2);
// 🗄️ CONFIGURACIÓN DE BASE DE DATOS
builder.HasOne<PropietarioEntity>()
.WithMany()
.HasForeignKey(m => m.PropietarioId);
}
}
�� Puntos Clave de la Infraestructura:
- ✅ Implementa las interfaces definidas en el dominio
- ✅ Maneja Entity Framework, APIs externas, archivos
- ✅ Convierte entre entidades de dominio y entidades de persistencia
- ✅ Puede cambiarse sin afectar el dominio
🌐 Capa de API - La Interfaz HTTP
La API expone los endpoints REST para que otros sistemas puedan usar nuestra aplicación:
// �� VETCAREPRO API - Endpoints REST
[ApiController]
[Route("api/[controller]")]
public class MascotasController : ControllerBase
{
private readonly IMediator _mediator;
public MascotasController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<ActionResult<Guid>> CreateMascota([FromBody] CreateMascotaRequest request)
{
// �� CONVERTIR Request → Command
var command = new CreateMascotaCommand(
request.Nombre,
request.Tipo,
request.Raza,
request.Sexo,
request.FechaNacimiento,
request.Peso,
request.Color,
request.PropietarioId,
request.Observaciones
);
// ⚡ EJECUTAR usando la capa de aplicación
var result = await _mediator.Send(command);
if (result.IsFailure)
return BadRequest(result.Error);
return CreatedAtAction(nameof(GetMascota), new { id = result.Value }, result.Value);
}
[HttpGet("{id}")]
public async Task<ActionResult<MascotaDto>> GetMascota(Guid id)
{
var query = new GetMascotaByIdQuery(id);
var mascota = await _mediator.Send(query);
if (mascota == null)
return NotFound();
return Ok(mascota);
}
}
// 📝 DTOs para la API
public record CreateMascotaRequest(
string Nombre,
TipoMascota Tipo,
string Raza,
SexoMascota Sexo,
DateTime FechaNacimiento,
decimal Peso,
string Color,
Guid PropietarioId,
string? Observaciones
);
🔑 Puntos Clave de la API:
- ✅ Expone endpoints REST
- ✅ Convierte HTTP requests/responses
- ✅ No contiene lógica de negocio
- ✅ Usa MediatR para delegar a la aplicación
🎨 Capa Web - La Interfaz de Usuario
La Web proporciona la interfaz gráfica para que los usuarios interactúen:
// 🎨 VETCAREPRO WEB - Interfaz de usuario con Blazor
@page "/mascotas"
@inject IVetCareProApiService ApiService
<MudContainer>
<MudText Typo="Typo.h3">🐕 Gestión de Mascotas</MudText>
<MudCard>
<MudCardContent>
<MudTextField @bind-Value="_searchTerm"
Label="Buscar mascota..."
AdornmentIcon="@Icons.Material.Filled.Search" />
<MudButton Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenCreateDialog">
Registrar Mascota
</MudButton>
@if (_mascotas.Any())
{
<MudTable Items="@_mascotas">
<HeaderContent>
<MudTh>Nombre</MudTh>
<MudTh>Tipo</MudTh>
<MudTh>Edad</MudTh>
<MudTh>Acciones</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Nombre</MudTd>
<MudTd>@context.Tipo</MudTd>
<MudTd>@context.EdadEnAnios años</MudTd>
<MudTd>
<MudButton Size="Size.Small"
OnClick="@(() => VerDetalle(context.Id))">
Ver
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
}
</MudCardContent>
</MudCard>
</MudContainer>
@code {
private List<MascotaDto> _mascotas = new();
private string _searchTerm = "";
protected override async Task OnInitializedAsync()
{
await LoadMascotas();
}
private async Task LoadMascotas()
{
// 🌐 LLAMAR a la API
_mascotas = (await ApiService.GetMascotasAsync()).ToList();
StateHasChanged();
}
}
🔑 Puntos Clave de la Web:
- ✅ Interfaz de usuario moderna con MudBlazor
- ✅ Se comunica con la API via HTTP
- ✅ No contiene lógica de negocio
- ✅ Experiencia de usuario rica e interactiva
🎯 Flujo Completo: Cómo Funcionan las Capas Juntas
Ejemplo: Registrar una Nueva Mascota
graph TD
A[👤 Usuario llena formulario] --> B[�� Web: Envía datos a API]
B --> C[🌐 API: Recibe HTTP POST]
C --> D[⚡ Application: Crea Command]
D --> E[🏥 Domain: Valida reglas de negocio]
E --> F[💾 Infrastructure: Guarda en BD]
F --> G[🔄 Respuesta vuelve por las capas]
G --> H[👤 Usuario ve confirmación]
1. �� Usuario en la Web:
// Usuario llena formulario y hace clic en "Registrar"
var command = new CreateMascotaCommand("Max", TipoMascota.Perro, "Golden Retriever", ...);
2. 🌐 API recibe la petición:
[HttpPost]
public async Task<ActionResult<Guid>> CreateMascota([FromBody] CreateMascotaRequest request)
{
var command = MapToCommand(request);
var result = await _mediator.Send(command); // ⚡ Delega a Application
return Ok(result.Value);
}
3. ⚡ Application orquesta:
public async Task<Result<Guid>> Handle(CreateMascotaCommand request, CancellationToken cancellationToken)
{
var mascota = Mascota.Create(...); // �� Usa el dominio
await _mascotaRepository.AddAsync(mascota); // 💾 Usa infraestructura
return Result.Success(mascota.Id);
}
4. 🏥 Domain valida reglas:
public static Mascota Create(string nombre, TipoMascota tipo, ...)
{
if (string.IsNullOrEmpty(nombre))
throw new ArgumentException("El nombre es obligatorio");
if (peso <= 0)
throw new ArgumentException("El peso debe ser positivo");
return new Mascota(nombre, tipo, ...);
}
5. 💾 Infrastructure persiste:
public async Task AddAsync(Mascota mascota)
{
var entity = MapToEntity(mascota);
_context.Mascotas.Add(entity);
await _context.SaveChangesAsync();
}
🏆 Beneficios de esta Arquitectura
✅ 1. Dominio Independiente
// �� El dominio NO depende de nada externo
public class Mascota
{
// ✅ Solo lógica de negocio pura
public bool PuedeVacunarse() => EdadEnMeses >= 2;
// ❌ NO hay referencias a Entity Framework
// ❌ NO hay referencias a APIs
// ❌ NO hay referencias a frameworks
}
✅ 2. Fácil Testing
[Test]
public void Mascota_PuedeVacunarse_CuandoEsMayorDe2Meses()
{
// ✅ Test puro del dominio - sin configuraciones
var mascota = Mascota.Create("Max", TipoMascota.Perro, "Golden",
SexoMascota.Macho, DateTime.Today.AddMonths(-3),
15.5m, "Dorado", Guid.NewGuid());
Assert.That(mascota.PuedeVacunarse(), Is.True);
}
✅ 3. Cambios Tecnológicos Fáciles
// 🔄 Cambiar de SQL Server a PostgreSQL
// Solo cambia la Infraestructura, el dominio permanece igual
// 🔄 Cambiar de Blazor a React
// Solo cambia la Web, la lógica de negocio permanece
// 🔄 Cambiar de REST a GraphQL
// Solo cambia la API, el dominio permanece
🎓 Resumen para los Estudiantes
�� Conceptos Clave:
- 🏥 Dominio = Reglas de Negocio
- Es el "cerebro" de la aplicación
- Contiene las reglas más importantes
- NO depende de nada externo
- ⚡ Aplicación = Orquestador
- Coordina las operaciones
- Conecta dominio con infraestructura
- Maneja validaciones y mapeos
- 💾 Infraestructura = Almacén
- Persiste datos en base de datos
- Conecta con APIs externas
- Implementa las interfaces del dominio
- 🌐 API = Interfaz HTTP
- Expone endpoints REST
- Convierte HTTP a comandos
- No contiene lógica de negocio
- �� Web = Interfaz de Usuario
- Interfaz gráfica moderna
- Se comunica con la API
- Experiencia de usuario rica
�� Flujo de Dependencias:
🎨 Web → 🌐 API → ⚡ Application → 🏥 Domain
↗️ 💾 Infrastructure ↗️
¡La magia está en que el dominio está en el centro y todo lo demás depende de él, no al revés! ✨