Patrón Repositorio con Inversión de Dependencias (DIP)
Se definen dos familias de abstracciones en el backend, cumpliendo el principio DIP de SOLID: el controlador/servicio depende únicamente de interfaces, nunca de la implementación concreta de acceso a datos.
ICatalogRepository: opera siempre contra la base de datos de control plane (resolución de rutas, cuotas, alta/baja de tenants).ITenantRepository<T>: su implementación concreta solo invoca Stored Procedures o Views de la base de datos tenant resuelta para la petición actual. Nunca arma SQL dinámico ni valida reglas de negocio en C#.
public interface IReservaRepository
{
Task<ReservaDto> CrearAsync(CrearReservaRequest request);
Task<IEnumerable<ReservaDto>> ListarAsync(int tenantId);
}
public class ReservaRepository : IReservaRepository
{
private readonly ITenantConnectionFactory _connFactory;
public async Task<ReservaDto> CrearAsync(CrearReservaRequest r)
{
using var conn = _connFactory.GetConnection();
return await conn.QuerySingleAsync<ReservaDto>(
"sp_CrearReserva",
new { r.Fecha, r.ClienteId, r.RecursoId },
commandType: CommandType.StoredProcedure);
}
}
Por qué así
Se elige Dapper (vía ADO.NET) en lugar de EF Core para el data plane porque no hay entidades de negocio que mapear ni migraciones que versionar desde el backend: toda la estructura la define y evoluciona el SP. EF Core aportaría una capa de abstracción sin beneficio real y tentaría a mover lógica de negocio hacia LINQ, violando la regla de oro del reto. Dapper obliga, por diseño, a llamar explícitamente al SP.