Web API: centralizza la logica delle transazioni commerciali: buona idea?

1

Sviluppiamo su un'API Web ASP.NET in cui utilizziamo il modello "Unità di lavoro / deposito":

I nostri controllori hanno questo aspetto:

public class MyController : Controller
{
    private IUnitOfWork _unitOfWork;
    private IMyService _myService;

    public MyController(IUnitOfWork unitOfWork, IMyService myService)
    {
        _unitOfWork = unitOfWork;
        _myService = myService;
    }

    [HttpPost]
    public IActionResult CreateItem(Item item)
    {
        try
        {
            _unitOfWork.BeginTransaction();
            _myService.CreateItem(item);
            _unitOfWork.Commit();
        }
        catch (Exception e)
        {
            // Transaction failed, we must rollback to keep a consistent state
            _unitOfWork.Rollback();

            // Logging
            //...

            // Return HTTP 500 Error
            return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError);
        }

        return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status200OK);
    }
}

Avremo più controller con la stessa logica:

  • Avvia una transazione
  • Esegui la logica aziendale
  • Conferma le modifiche
  • Gestire eventuali eccezioni (rollback, log, restituire lo stato HTTP appropriato)

Quindi, ci stiamo chiedendo se possiamo "centralizzare" questo codice per evitare la duplicazione

Solo la logica aziendale sarà diversa

In questa classe:

_myService.CreateItem(item);

È davvero una buona idea?

E se sì quale modello sarebbe il migliore?

Modifica

Dopo ulteriori indagini, sembra che l'unica cosa che dobbiamo davvero centralizzare sia la gestione delle eccezioni

In questo caso avremmo questo tipo di controller:

public class MyController : Controller
{
    private IUnitOfWork _unitOfWork;
    private IMyService _myService;

    public MyController(IUnitOfWork unitOfWork, IMyService myService)
    {
        _unitOfWork = unitOfWork;
        _myService = myService;
    }

    [HttpPost]
    public void CreateItem(Item item)
    {
        using (var uow = _unitOfWork.BeginTransaction())
        {
            _myService.CreateItem(item);
            _unitOfWork.Commit();
        }
    }
}

La gestione delle eccezioni sarebbe centralizzata con ExceptionFilter come spiegato qui:

link

Per il rollback, sembra che non sia necessario gestirlo manualmente:

link

    
posta Sylvain 03.04.2018 - 10:21
fonte

2 risposte

2

Naturalmente puoi spostare questo codice in una classe che gestisce questo per te, ad esempio utilizzando la logica di business come azione ( link ) o qualcosa del genere. Questo potrebbe sicuramente essere una soluzione per la gestione delle transazioni e probabilmente per i codici di risultato HTTP 500 e 200.

public class MyTransactionManager  // Think of proper names
{
    public void Execute(Action action)
    {
        try
        {
            _unitOfWork.BeginTransaction();
            action();
            _unitOfWork.Commit();
        }
        catch (Exception e)
        {
            // .. handling .. 
        }
    }
}

Chiamandolo dal controller:

[HttpPost]
public IActionResult CreateItem(Item item)
{
    Action action = delegate() { _myService.CreateItem(item); } ;
    _transactionManager.Execute(action);
}

potrebbe essere necessario verificare i dettagli sulla chiamata all'azione, non averlo provato).

Tuttavia, dovrai anche gestire alcuni errori per situazioni specifiche. Qualsiasi chiamata alla tua WebAPI può avere diverse situazioni in cui restituire quale codice di errore HTTP come i 4xx. Questi controlli saranno sempre nel controller stesso.

Non essere tentato di restituire StatusCodes dalla tua logica aziendale.

    
risposta data 03.04.2018 - 12:07
fonte
2

Is it really a good idea ?

Dipende dal tuo caso d'uso, tuttavia se puoi centralizzarlo hai meno codice da mantenere.

And if yes what pattern would be the best ?

Potresti usare un filtro azione, se è usato in ogni chiamata Api, fallo diventare un filtro globale.

[AttributeUsage(AttributeTargets.Method)]
public class TransactionAttribute : ActionFilterAttribute
{
    private IUnitOfWork unitOfWork;

    public TransactionAttribute()
    {
        unitOfWork = //Resolve the unit of work
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _unitOfWork.BeginTransaction();
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Exception == null)
        {
            try
            {
                _unitOfWork.Commit();
            }
            catch(Exception ex)
            {
                _unitOfWork.Rollback();
            }
        }
        else
        {
            _unitOfWork.Rollback();
        }
    }
}

Puoi usare il filtro in questo modo,

public class MyController : Controller
{
    private IMyService _myService;

    public MyController(IMyService myService)
    {
        _myService = myService;
    }

    [HttpPost]
    [Transaction]
    public IActionResult CreateItem(Item item)
    {
        _myService.CreateItem(item);
        //return the status code
    }
}
    
risposta data 03.04.2018 - 12:04
fonte

Leggi altre domande sui tag