54 lines
2.1 KiB
C#
54 lines
2.1 KiB
C#
using SIGCM2.Application.Abstractions;
|
||
using SIGCM2.Application.Abstractions.Persistence;
|
||
using SIGCM2.Domain.Exceptions;
|
||
|
||
namespace SIGCM2.Application.PuntosDeVenta.Reservar;
|
||
|
||
/// <summary>
|
||
/// Reserva el próximo número correlativo para (PdvId × TipoComprobante) ejecutando
|
||
/// usp_ReservarNumeroComprobante vía el repositorio.
|
||
///
|
||
/// NOTAS DE DISEÑO (AD4, AD9):
|
||
/// - NO se envuelve en TransactionScope: el SP ya es atómico bajo SERIALIZABLE.
|
||
/// Un TransactionScope ambiente aquí escalaría a DTC → innecesario.
|
||
/// - NO usa Polly: no está en el proyecto. Retry deadlock con bucle simple.
|
||
/// - Infrastructure traduce SqlException 1205 → DeadlockTransientException.
|
||
/// - Backoff en ms: [50, 150, 450] — 3 intentos máximo.
|
||
/// - La auditoría de reservas corre solo vía Temporal Tables (AD8).
|
||
/// </summary>
|
||
public sealed class ReservarNumeroCommandHandler : ICommandHandler<ReservarNumeroCommand, ReservaNumeroDto>
|
||
{
|
||
private readonly IPuntoDeVentaRepository _repo;
|
||
private readonly int[] _deadlockBackoffMs;
|
||
|
||
private static readonly int[] DefaultBackoffMs = [50, 150, 450];
|
||
|
||
public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo)
|
||
: this(repo, DefaultBackoffMs) { }
|
||
|
||
/// <summary>Constructor with custom backoff for testing (e.g., [0,0,0] for fast tests).</summary>
|
||
public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo, int[] deadlockBackoffMs)
|
||
{
|
||
_repo = repo;
|
||
_deadlockBackoffMs = deadlockBackoffMs;
|
||
}
|
||
|
||
public async Task<ReservaNumeroDto> Handle(ReservarNumeroCommand command)
|
||
{
|
||
for (var i = 0; ; i++)
|
||
{
|
||
try
|
||
{
|
||
var numero = await _repo.ReservarNumeroAsync(command.PuntoDeVentaId, command.TipoComprobante);
|
||
return new ReservaNumeroDto(command.TipoComprobante, numero);
|
||
}
|
||
catch (DeadlockTransientException) when (i < _deadlockBackoffMs.Length)
|
||
{
|
||
// Deadlock — retry with backoff
|
||
await Task.Delay(_deadlockBackoffMs[i]);
|
||
}
|
||
// All other exceptions bubble up immediately
|
||
}
|
||
}
|
||
}
|