Application Layer
Implementation Status : The Application layer has a complete CQRS folder structure with Command and Query files, but these are currently stub classes. The structure is ready for implementation.
The Application layer will orchestrate business workflows using the CQRS (Command Query Responsibility Segregation) pattern. It will contain use cases that coordinate domain entities, repositories, and external services.
CQRS Pattern
SGRH separates write operations (Commands) from read operations (Queries):
Commands Modify state, enforce business rules, and coordinate domain entities
Queries Read data optimized for specific UI needs without loading full aggregates
Project Structure
SGRH.Application/
├── Features/
│ ├── Reservas/
│ │ ├── Commands/
│ │ │ ├── CrearReserva.cs
│ │ │ ├── ActualizarReserva.cs
│ │ │ ├── ConfirmarReserva.cs
│ │ │ ├── CancelarReserva.cs
│ │ │ ├── CheckIn.cs
│ │ │ └── CheckOut.cs
│ │ └── Queries/
│ │ ├── ListarReservas.cs
│ │ └── ObtenerReservaPorId.cs
│ ├── Habitaciones/
│ │ ├── Commands/
│ │ │ ├── CrearHabitacion.cs
│ │ │ ├── ActualizarHabitacion.cs
│ │ │ └── CambiarEstadoHabitacion.cs
│ │ └── Queries/
│ │ ├── ListarHabitaciones.cs
│ │ └── ObtenerHabitacionPorId.cs
│ ├── Clientes/
│ │ ├── Commands/
│ │ │ ├── CrearCliente.cs
│ │ │ ├── ActualizarCliente.cs
│ │ │ └── EliminarCliente.cs
│ │ └── Queries/
│ │ ├── ListarClientes.cs
│ │ └── ObtenerClientePorId.cs
│ ├── ServiciosAdicionales/
│ ├── TarifasTemporadas/
│ ├── Reportes/
│ └── Auditoria/
├── Common/
│ ├── Mappings/ # Entity to DTO mappings
│ └── Exceptions/ # Application-level exceptions
└── DependencyInjection.cs
Command Pattern
Command Structure
Commands are represented as classes with specific responsibilities:
// Typical command structure (conceptual - actual files may vary)
public class CrearReservaCommand
{
public int ClienteId { get ; set ; }
public DateTime FechaEntrada { get ; set ; }
public DateTime FechaSalida { get ; set ; }
public List < int > HabitacionIds { get ; set ; }
}
public class CrearReservaHandler
{
private readonly IReservaRepository _reservaRepo ;
private readonly IReservaDomainPolicy _policy ;
private readonly IUnitOfWork _uow ;
public async Task < ReservaDto > Handle ( CrearReservaCommand cmd , CancellationToken ct )
{
// 1. Create domain entity
var reserva = new Reserva ( cmd . ClienteId , cmd . FechaEntrada , cmd . FechaSalida );
// 2. Add rooms through domain methods
foreach ( var habitacionId in cmd . HabitacionIds )
{
reserva . AgregarHabitacion ( habitacionId , _policy );
}
// 3. Persist
await _reservaRepo . AddAsync ( reserva , ct );
await _uow . SaveChangesAsync ( ct );
// 4. Return DTO
return MapToDto ( reserva );
}
}
Commands orchestrate domain entities but don’t contain business logic themselves. Logic stays in the Domain layer.
Command Examples by Feature
Reservations
Rooms
Clients
Services & Rates
Creates a new reservation with initial room assignments. Flow:
Validate client exists
Create Reserva domain entity (state: Pendiente)
Add rooms through AgregarHabitacion() method
Policy validates room availability and pricing
Persist and return DTO
Transitions reservation from Pendiente → Confirmada. Flow:
Load reservation with details
Call reserva.Confirmar() domain method
Domain enforces business rules (must have rooms)
Save changes
Handles guest arrival and departure. Flow (CheckIn):
Load confirmed reservation
Validate dates match current date
Change room states to Ocupada
Update reservation state if needed
Generate audit log
Cancels an existing reservation. Flow:
Load reservation
Call reserva.Cancelar() domain method
Domain prevents canceling finalized reservations
Release room allocations
Save and notify client (via email service)
Registers a new room in the system. Flow:
Validate category exists
Check room number uniqueness
Create Habitacion entity (initial state: Disponible)
Persist
Changes room status (Disponible, Mantenimiento, etc.). Flow:
Load room entity
Call habitacion.CambiarEstado() method
Domain creates temporal history record
Validates state transition rules
Save changes
Registers a new customer. Flow:
Validate NationalId uniqueness
Create Cliente entity
Guard clauses validate email format, phone, etc.
Persist
Updates customer information. Flow:
Load client entity
Call cliente.ActualizarDatos() method
Domain validates new data
Save changes
Creates pricing for a room category in a season. Flow:
Validate category and season exist
Check for overlapping rates
Create TarifaTemporada entity
Persist
Adds a new hotel service (Spa, Restaurant, etc.). Flow:
Create ServicioAdicional entity
Optionally enable for specific seasons
Set pricing by room category
Persist
Query Pattern
Query Structure
Queries bypass domain entities for optimized reads:
// Conceptual query structure
public class ListarReservasQuery
{
public DateTime ? FechaDesde { get ; set ; }
public DateTime ? FechaHasta { get ; set ; }
public EstadoReserva ? Estado { get ; set ; }
public int PageNumber { get ; set ; } = 1 ;
public int PageSize { get ; set ; } = 20 ;
}
public class ListarReservasHandler
{
private readonly SGRHDbContext _db ;
public async Task < PagedResult < ReservaListDto >> Handle (
ListarReservasQuery query , CancellationToken ct )
{
var queryable = _db . Reservas
. Include ( r => r . Habitaciones )
. AsQueryable ();
// Apply filters
if ( query . FechaDesde . HasValue )
queryable = queryable . Where ( r => r . FechaEntrada >= query . FechaDesde );
if ( query . Estado . HasValue )
queryable = queryable . Where ( r => r . EstadoReserva == query . Estado );
// Project to DTO and paginate
var total = await queryable . CountAsync ( ct );
var items = await queryable
. OrderByDescending ( r => r . FechaReserva )
. Skip (( query . PageNumber - 1 ) * query . PageSize )
. Take ( query . PageSize )
. Select ( r => new ReservaListDto
{
ReservaId = r . ReservaId ,
ClienteId = r . ClienteId ,
FechaEntrada = r . FechaEntrada ,
FechaSalida = r . FechaSalida ,
EstadoReserva = r . EstadoReserva . ToString (),
CostoTotal = r . CostoTotal ,
NumeroHabitaciones = r . Habitaciones . Count
})
. ToListAsync ( ct );
return new PagedResult < ReservaListDto >( items , total , query . PageNumber , query . PageSize );
}
}
Queries use direct EF Core projections to DTOs for performance. No need to load full entity graphs.
Query Examples
Reservations
Rooms
Reports
Audit
ListarReservas : Paginated list with filters (date range, status, client)
ObtenerReservaPorId : Full reservation details including rooms and services
ListarHabitaciones : Available rooms by date range and category
ObtenerHabitacionPorId : Room details with current status
GenerarReporteOcupacion : Room occupancy statistics by period
GenerarReporteIngresos : Revenue reports with breakdowns
GenerarReporteReservas : Reservation analytics and trends
ObtenerAuditoria : Query audit logs by entity, action, user, and date range
Mapping Strategies
The application layer uses mapping extensions to convert entities to DTOs:
SGRH.Application/Common/Mappings/
├── Clientes/
│ └── ClienteMap.cs
├── Habitaciones/
│ └── HabitacionMap.cs
├── Reservas/
│ ├── ReservaMap.cs
│ └── ReservaDetalleMap.cs
├── ServiciosAdicionales/
│ └── ServicioAdicionalMap.cs
└── TarifasTemporadas/
├── TarifaMap.cs
└── TemporadaMap.cs
Manual Mapping
EF Projection
public static class ReservaMap
{
public static ReservaDto ToDto ( this Reserva reserva )
{
return new ReservaDto
{
ReservaId = reserva . ReservaId ,
ClienteId = reserva . ClienteId ,
EstadoReserva = reserva . EstadoReserva . ToString (),
FechaReserva = reserva . FechaReserva ,
FechaEntrada = reserva . FechaEntrada ,
FechaSalida = reserva . FechaSalida ,
CostoTotal = reserva . CostoTotal ,
Habitaciones = reserva . Habitaciones . Select ( h => h . ToDto ()). ToList (),
Servicios = reserva . Servicios . Select ( s => s . ToDto ()). ToList ()
};
}
}
Exception Handling
Application-level exceptions wrap domain exceptions:
SGRH . Application / Common / Exceptions /
├── ValidationException . cs # Input validation failures
├── NotFoundException . cs # Entity not found
└── ForbiddenException . cs # Authorization failures
Unit of Work Pattern
Commands coordinate multiple repository operations within a transaction:
public interface IUnitOfWork
{
Task < int > SaveChangesAsync ( CancellationToken ct = default );
Task BeginTransactionAsync ( CancellationToken ct = default );
Task CommitAsync ( CancellationToken ct = default );
Task RollbackAsync ( CancellationToken ct = default );
}
// Usage in command handler
public async Task Handle ( CrearReservaCommand cmd , CancellationToken ct )
{
await _uow . BeginTransactionAsync ( ct );
try
{
// Multiple operations
await _reservaRepo . AddAsync ( reserva , ct );
await _auditoriaRepo . AddAsync ( audit , ct );
await _uow . CommitAsync ( ct );
}
catch
{
await _uow . RollbackAsync ( ct );
throw ;
}
}
Feature Organization
Reservas
Crear, Actualizar, Confirmar, Cancelar
CheckIn / CheckOut workflows
Service and room management
Habitaciones
CRUD operations
State changes with history tracking
Availability queries
Clientes
Customer registration and updates
Reservation history queries
Servicios Adicionales
Service catalog management
Seasonal availability configuration
Category-based pricing
Tarifas y Temporadas
Dynamic pricing by season
Room category rate management
Reportes
Occupancy reports
Revenue analytics
Reservation trends
Auditoría
Event logging
Change tracking
Compliance reports
Security
Login / RefreshToken
User profile queries
Key Design Decisions
CQRS Separation Commands modify state through domain entities; Queries read optimized DTOs
Thin Handlers Application handlers orchestrate but don’t contain business logic
DTO Projections Queries project directly to DTOs to avoid loading full aggregates
Domain Policies Complex rules delegated to policy implementations injected via DI
Domain Layer Understand the entities being orchestrated
API Layer See how controllers invoke commands/queries
Infrastructure Learn about persistence and external services