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