Skip to main content

Buenas prácticas de la capa de servicio

La capa de servicios encapsula las operaciones de negocio y orquesta el acceso a datos para los controladores, las tareas programadas y otros componentes. Los servicios se ejecutan en el mismo ámbito de inyección de dependencias que los controladores y las tareas, trabajan con entidades de dominio y dejan las preocupaciones de presentación a las capas superiores.

Mantén los servicios con propósito

  • Introduce un servicio solo cuando la lógica abarque varias consultas/repositorios o requiera preocupaciones transversales como caché o mensajería.
  • No envuelvas una única consulta en un método de servicio; en ese caso, expón la consulta directamente.

Diseña interfaces limpias

  • Empareja interfaz e implementación (por ejemplo, IPrestamoServicio / PrestamoServicio) para poder sustituir o simular la implementación en pruebas.
  • Mantén los métodos cohesivos y asíncronos: sufija los métodos asíncronos con Async y acepta un CancellationToken en las operaciones de E/S.
  • Cuando una interfaz crece demasiado, segrégala por responsabilidad (Crono lo hace, por ejemplo, con IPrestamoServicio, dividido en interfaces específicas como IPrestamoConsultaServicio, IPrestamoLiquidacionServicio, etc.).

Minimiza dependencias

  • Inyecta por el constructor solo los colaboradores que realmente necesitas; evita el patrón service locator.
  • Nunca inyectes controladores, ayudantes de Razor u otros tipos de presentación.
  • Trabaja con modelos de dominio y con el CronoDbContexto.

Evita cadenas de servicios

  • Los servicios deben ser sin estado e independientes. Cuando uno necesita funcionalidad de otro, extrae un ayudante compartido o un método de dominio.
  • Trae los datos por lotes en vez de iterar elemento por elemento llamando a otro servicio en cada iteración.

Ejemplo

public interface IPrecioServicio
{
Task<decimal> ObtenerPrecioAsync(int prestamoId, CancellationToken cancelToken = default);
}

public class PrecioServicio : IPrecioServicio
{
private readonly CronoDbContexto _db;
private readonly ICacheAdministrador _cache;

// Registrador opcional inyectado por propiedad
public ILogger Registrador { get; set; } = NullLogger.Instance;

public PrecioServicio(CronoDbContexto db, ICacheAdministrador cache)
{
_db = db;
_cache = cache;
}

public async Task<decimal> ObtenerPrecioAsync(int prestamoId, CancellationToken cancelToken = default)
{
var clave = $"prestamo-precio-{prestamoId}";
return await _cache.ObtenerAsync(clave, async () =>
{
var prestamo = await _db.Prestamos.FindByIdAsync(prestamoId, cancelToken);
return prestamo.Monto;
});
}
}

Registro y caché

  • Expón un registrador opcional para que el contenedor inyecte una instancia contextual: public ILogger Registrador { get; set; } = NullLogger.Instance; (ver Registro).
  • Usa ICacheAdministrador para consultas costosas e invalida las entradas cuando cambien los datos (ver Caché).

Pruebas

  • Mantén las interfaces pequeñas para simplificar el mocking y las pruebas unitarias.
  • Evita el estado estático salvo que sea thread-safe y compartido de forma intencional.