Skip to main content

Layer Overview

ApiTickets implements Clean Architecture through four distinct layers, each with specific responsibilities. Dependencies flow inward, with the Domain layer at the center having no external dependencies.

Domain

Core business logic and entities

Application

Service configuration and orchestration

Infrastructure

Database and external service implementations

Persistence

Data access with CQRS pattern

Domain Layer

The Domain layer is the heart of the application, containing all business entities and core domain logic. It has zero dependencies on other layers or external frameworks.

Location

/Domain
├── Entities/
│   ├── TicketE/
│   │   └── TicketE.cs
│   ├── UsuarioE/
│   │   └── UsuarioE.cs
│   └── CatalogoE/
│       ├── AreaE.cs
│       ├── PrioridadE.cs
│       └── EstadoTicketE.cs
├── DTOs/
│   ├── TicketD/
│   ├── UsuarioD/
│   └── CatalogoD/
└── Utilidades/
    └── Password.cs

Responsibilities

Define business entities with their properties and validation rules.
namespace Domain.Entities.TicketE
{
    [Table("tickets")]
    public class TicketE
    {
        [Key]
        [Column("id_ticket")]
        public Guid? IdTicket { get; set; } = Guid.NewGuid();

        [Column("codigo_seguimiento")]
        public required string Codigo { get; set; }

        [Column("titulo")]
        public required string Titulo { get; set; }

        [Column("descripcion")]
        public required string Descripcion { get; set; }

        [Column("id_usuario")]
        public required Guid UsuarioId { get; set; }

        [Column("id_area")]
        public required int AreaId { get; set; }

        [Column("id_prioridad")]
        public required int PrioridadId { get; set; }

        [Column("id_estado")]
        public required int EstadoId { get; set; }

        [Column("fecha_creacion")]
        public required DateTime FechaCreacion { get; set; }

        [Column("fecha_actualizacion")]
        public required DateTime FechaActualizacion { get; set; }
    }
}
Provide clean data structures for transferring data between layers.
namespace Domain.DTOs.TicketD
{
    public class TicketDto
    {
        public Guid? IdTicket { get; set; }
        public string? Codigo { get; set; }
        public required string Titulo { get; set; }
        public required string Descripcion { get; set; }
        public required Guid UsuarioId { get; set; }
        public required int AreaId { get; set; }
        public required int PrioridadId { get; set; }
        public required int EstadoId { get; set; }
        public DateTime FechaCreacion { get; set; }
        public DateTime FechaActualizacion { get; set; }

        public static TicketDto CrearDTO(TicketE ticketE)
        {
            return new TicketDto
            {
                IdTicket = ticketE.IdTicket,
                Codigo = ticketE.Codigo,
                Titulo = ticketE.Titulo,
                Descripcion = ticketE.Descripcion,
                UsuarioId = ticketE.UsuarioId,
                AreaId = ticketE.AreaId,
                PrioridadId = ticketE.PrioridadId,
                EstadoId = ticketE.EstadoId,
                FechaCreacion = ticketE.FechaCreacion,
                FechaActualizacion = ticketE.FechaActualizacion,
            };
        }
    }
}
Business-specific utilities like password hashing.
namespace Domain.Utilidades
{
    public interface IPassword
    {
        string Hash(string password);
        bool Verify(string passwordHash, string inputPassword);
    }
    
    public class Password : IPassword
    {
        // Implementation for password hashing and verification
    }
}
The Domain layer uses only .NET standard libraries and data annotations. No external framework dependencies.

Application Layer

The Application layer orchestrates the application flow, configures dependency injection, and sets up authentication. It resides within the API project.

Location

/API/Application/
└── StartupSetup.cs

Responsibilities

1

Service Registration

Registers all application services with the dependency injection container.
public static class StartupSetup
{
    public static IServiceCollection AddStartupSetup(
        this IServiceCollection service, 
        IConfiguration configuration)
    {
        // Queries 
        service.AddTransient<ICatalogoQueries, CatalogoQueries>();
        service.AddTransient<IUsuarioQueries, UsuarioQueries>();
        service.AddTransient<ITicketQueries, TicketQueries>();

        //Commands
        service.AddTransient<ITicketCommands, TicketCommands>();

        // Utilidades
        service.AddScoped<IPassword, Password>();

        // Servicios
        service.AddScoped<IGenerarToken, GenerarToken>();
        
        // ... JWT configuration
    }
}
2

JWT Authentication Setup

Configures JWT Bearer authentication for securing API endpoints.
// Autenticacion jwt
var key = configuration.GetValue<string>("Jwt:key");
var keyBytes = Encoding.ASCII.GetBytes(key);

service.AddAuthentication(config =>
{
    config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(config =>
{
    config.RequireHttpsMetadata = false;
    config.SaveToken = true;
    config.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    };
});
3

Dependency Injection Pattern

Uses constructor injection for loose coupling and testability.Service Lifetimes:
  • AddTransient: New instance per request (Queries, Commands)
  • AddScoped: Single instance per HTTP request (Password, Token generation)

Integration with Program.cs

// API/Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Setup CORS
var proveedor = builder.Services.BuildServiceProvider();
var configuration = proveedor.GetRequiredService<IConfiguration>();

builder.Services.AddCors(opciones =>
{
    var web = configuration.GetValue<string>("web");
    opciones.AddDefaultPolicy(builder => {
        builder.WithOrigins(web)
               .AllowAnyMethod()
               .AllowAnyHeader()
               .AllowCredentials();
    });
});

// Call Application layer setup
builder.Services.AddStartupSetup(builder.Configuration);

var app = builder.Build();

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Infrastructure Layer

The Infrastructure layer provides implementations for external concerns such as database access and security services.

Location

/Infrastructure
├── DBContext.cs
└── Seguridad/
    └── JWTGenerador.cs

Responsibilities

Manages database connection and entity configurations.
namespace Infrastructure
{
    public class DBContext : DbContext
    {
        private readonly string _connection;

        public DBContext(string connection)
        {
            _connection = connection;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql(_connection);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }

        // DbSets for entities
        public virtual DbSet<UsuarioE> UsuarioEs { get; set; }
        public virtual DbSet<AreaE> AreaEs { get; set; }
        public virtual DbSet<PrioridadE> PrioridadEs { get; set; }
        public virtual DbSet<EstadoTicketE> EstadoTicketEs { get; set; }
        public virtual DbSet<TicketE> TicketEs { get; set; }
    }
}
Generates secure JWT tokens for user authentication.
namespace Infrastructure.Seguridad
{
    public interface IGenerarToken
    {
        string GenerarTokenUsuario(UsuarioDto usuarioDto);
    }
    
    public class GenerarToken : IGenerarToken
    {
        private readonly IConfiguration _configuracion;

        public GenerarToken(IConfiguration configuration)
        {
            _configuracion = configuration;
        }

        public string GenerarTokenUsuario(UsuarioDto usuarioDto)
        {
            var key = _configuracion.GetValue<string>("Jwt:key");
            var keyBytes = Encoding.ASCII.GetBytes(key);

            var claims = new ClaimsIdentity();
            claims.AddClaim(new Claim(ClaimTypes.NameIdentifier, 
                Convert.ToString(usuarioDto.IdUsuario)));

            var credencialesToken = new SigningCredentials(
                new SymmetricSecurityKey(keyBytes),
                SecurityAlgorithms.HmacSha256Signature
            );

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = claims,
                Expires = DateTime.UtcNow.AddDays(10),
                SigningCredentials = credencialesToken
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var tokenConfig = tokenHandler.CreateToken(tokenDescriptor);
            string tokenCreado = tokenHandler.WriteToken(tokenConfig);

            return tokenCreado;
        }
    }
}
The Infrastructure layer depends on the Domain layer for entity definitions but provides concrete implementations for external services.

Persistence Layer

The Persistence layer implements the CQRS pattern (Command Query Responsibility Segregation), separating read and write operations for better performance and scalability.

Location

/Persistence
├── Queries/
│   ├── CatalogoQueries.cs
│   ├── UsuarioQueries.cs
│   └── TicketQueries.cs
└── Commands/
    └── TicketCommands.cs

CQRS Pattern

Queries (Read)

Handle all read operations using optimized queries

Commands (Write)

Handle all write operations (create, update, delete)

Query Implementation

Queries handle read operations with optimizations like AsNoTracking() for better performance:
namespace Persistence.Queries
{
    public interface ITicketQueries
    {
        Task<List<TicketDto>> ObtenerTicketsAsync();
        Task<ConsultarTicketDto> ObtenerTicketCodigoAsync(string codigo);
        Task<List<TicketDto>> ObtenerTicketIdUsurioAsync(Guid IdUsuario);
    }

    public class TicketQueries : ITicketQueries, IDisposable
    {
        private readonly DBContext _contexto;
        private readonly ILogger<TicketQueries> _logger;
        private readonly IConfiguration _configuracion;

        public TicketQueries(ILogger<TicketQueries> logger, IConfiguration configuration)
        {
            _logger = logger;
            _configuracion = configuration;
            string? Conexion = _configuracion.GetConnectionString("Conexion");
            _contexto = new DBContext(Conexion);
        }

        public async Task<List<TicketDto>> ObtenerTicketsAsync()
        {
            _logger.LogDebug("Iniciando {Metodo}", nameof(ObtenerTicketsAsync));
            try
            {
                var tickets = await _contexto.TicketEs
                    .AsNoTracking()
                    .Select(a => TicketDto.CrearDTO(a))
                    .ToListAsync();

                return tickets;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error ejecutando {Metodo}", nameof(ObtenerTicketsAsync));
                throw;
            }
        }

        public async Task<List<TicketDto>> ObtenerTicketIdUsurioAsync(Guid IdUsuario)
        {
            var tickets = await _contexto.TicketEs
                .AsNoTracking()
                .Where(t => t.UsuarioId == IdUsuario)
                .Select(t => TicketDto.CrearDTO(t))
                .ToListAsync();

            return tickets;
        }
    }
}

Command Implementation

Commands handle write operations with transaction support:
namespace Persistence.Commands
{
    public interface ITicketCommands
    {
        Task<RespuestaDto> CrearTicketAsync(CrearticketDto crearticketDto);
        Task<RespuestaDto> ActualizarEstadoTicketAsync(ActualizarEstadoTicketDto dto);
    }
    
    public class TicketCommands : ITicketCommands, IDisposable
    {
        private readonly DBContext _contexto;
        private readonly ILogger<TicketCommands> _logger;
        private readonly IConfiguration _configuracion;

        public TicketCommands(ILogger<TicketCommands> logger, IConfiguration configuration)
        {
            _logger = logger;
            _configuracion = configuration;
            string? Conexion = _configuracion.GetConnectionString("Conexion");
            _contexto = new DBContext(Conexion);
        }

        public async Task<RespuestaDto> CrearTicketAsync(CrearticketDto crearticketDto)
        {
            _logger.LogDebug("Iniciando {Metodo}", nameof(CrearTicketAsync));
            try
            {
                var ultimoTicket = await _contexto.Database
                    .SqlQuery<int>($"SELECT nextval('ticket_numero_seq') AS \"Value\"")
                    .SingleAsync();

                string codigo = ultimoTicket.ToString("D3");

                var ticket = new TicketDto
                {
                    Codigo = codigo,  
                    Titulo = crearticketDto.Titulo,
                    Descripcion = crearticketDto.Descripcion,
                    UsuarioId = crearticketDto.UsuarioId,
                    AreaId = crearticketDto.AreaId,
                    PrioridadId = crearticketDto.PrioridadId,
                    EstadoId = crearticketDto.EstadoId,
                    FechaCreacion = DateTime.UtcNow,
                    FechaActualizacion = DateTime.UtcNow,
                };

                var ticketE = TicketDto.CrearE(ticket);
                await _contexto.TicketEs.AddAsync(ticketE);
                await _contexto.SaveChangesAsync();

                return new RespuestaDto
                {
                    respuesta = true,
                    mensaje = "Ticket creado correctamente.",
                    ticket = codigo,
                };
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error ejecutando {Metodo}", nameof(CrearTicketAsync));
                throw;
            }
        }

        public async Task<RespuestaDto> ActualizarEstadoTicketAsync(
            ActualizarEstadoTicketDto dto)
        {
            var filasAfectadas = await _contexto.TicketEs
                .Where(t => t.IdTicket == dto.Id)
                .ExecuteUpdateAsync(setters => setters
                    .SetProperty(t => t.EstadoId, dto.EstadoId)
                    .SetProperty(t => t.FechaActualizacion, DateTime.UtcNow)
                );

            if (filasAfectadas == 0)
            {
                return new RespuestaDto
                {
                    respuesta = false,
                    mensaje = "No se encontró el ticket."
                };
            }

            return new RespuestaDto
            {
                respuesta = true,
                mensaje = "Estado del ticket actualizado correctamente."
            };
        }
    }
}

Benefits of CQRS

Performance

Optimized queries with AsNoTracking() for reads

Scalability

Independent scaling of read and write operations

Clarity

Clear separation between read and write logic

Flexibility

Different models for reading and writing data
Both Queries and Commands implement IDisposable to properly manage database context lifecycle.

Layer Dependencies Summary

┌─────────────────────────────────────────────┐
│              API Layer                      │
│  - Controllers                              │
│  - Program.cs                               │
└───────────────┬─────────────────────────────┘

        ┌───────┴────────┐
        │                │
        ▼                ▼
┌──────────────┐  ┌─────────────────┐
│ Application  │  │ Infrastructure  │
│  - Startup   │  │  - DBContext    │
│  - DI Config │  │  - JWT          │
└──────┬───────┘  └────────┬────────┘
       │                   │
       │                   │
       │         ┌─────────┴──────────┐
       │         │                    │
       ▼         ▼                    ▼
┌────────────────────┐      ┌────────────────┐
│   Persistence      │      │    Domain      │
│  - Queries         │─────▶│  - Entities    │
│  - Commands        │      │  - DTOs        │
└────────────────────┘      │  - Utilities   │
                            └────────────────┘

Next Steps

Entity Models

Learn about domain entities and their relationships

API Endpoints

Explore the REST API endpoints

Build docs developers (and LLMs) love