Skip to main content

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

Creates a new reservation with initial room assignments.Flow:
  1. Validate client exists
  2. Create Reserva domain entity (state: Pendiente)
  3. Add rooms through AgregarHabitacion() method
  4. Policy validates room availability and pricing
  5. Persist and return DTO
Transitions reservation from Pendiente → Confirmada.Flow:
  1. Load reservation with details
  2. Call reserva.Confirmar() domain method
  3. Domain enforces business rules (must have rooms)
  4. Save changes
Handles guest arrival and departure.Flow (CheckIn):
  1. Load confirmed reservation
  2. Validate dates match current date
  3. Change room states to Ocupada
  4. Update reservation state if needed
  5. Generate audit log
Cancels an existing reservation.Flow:
  1. Load reservation
  2. Call reserva.Cancelar() domain method
  3. Domain prevents canceling finalized reservations
  4. Release room allocations
  5. Save and notify client (via email service)

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

  • ListarReservas: Paginated list with filters (date range, status, client)
  • ObtenerReservaPorId: Full reservation details including rooms and services

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
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

Build docs developers (and LLMs) love