Migliorare la progettazione delle transizioni di stato in un'applicazione aziendale: il caso dell'annullamento

1

Devo caricare determinati oggetti dal DB e propagarli attraverso pochi stati e salvare dopo ogni propagazione di stato.

Cerchiamo di illustrare questo con l'esempio di un'entità Order . Ora devo caricare pochi ordini in sospeso da DB e Cancel in base a pochi criteri. Se questi criteri non corrispondono, non dovrei cancellarli.

Ho il codice seguente nel mio strato Application :

public void CancelPendingOrders() {
  var pendingOrders = _orderRepository.GetPendingOrders();
  _orderService.CancelOrders(pendingOrders);
}

public class CancelOrderCommand {
  //other classes which are used in OrderCanBeCancelled are injected in .ctor
  public void Execute(Order order) {
    if(CanExecute(order))    // OrderCanBeCancelled 
      order.SetStatus(OrderStatus.Cancelled);
    }
}

E il mio servizio di dominio come sotto

public class OrderCancelService : IOrderCancel{
  void CancelPendingOrders(Orders[] orders){
    foreach (var order in orders)
    {
      _orderCancelCommand.Execute(order);  
    }   
  } 
}

public class Order{
    public Order(OrderId orderId, OrderStatus orderStatus){
        // assigned to local private fields.
    }

    public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;
    internal void ChangeOrderStatus(OrderStatus status){
      var oldStatus = new OrderStatus(_status);
      _status = status;
      if(OrderStatusChanged != null){
         OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
     }
    }
}

Ho poche classi che ascoltano OrderStatusChangedEvent ed eseguono altre attività come ad esempio l'invio di una notifica e il salvataggio dello stato aggiornato nel database.

Se osservi la precedente classe Order non ho comandi separati per ogni OrderStatus come Cancel() ed è piuttosto anemica.

Ce l'ho in questo modo perché non voglio iniettare altri comandi e servizi nella classe Order , perché devo eseguire CanOrderBeCancelled per ogni cambio di stato.

Da quello che ho attualmente è abbastanza semplice per me introdurre una nuova classe Command per ogni stato se necessario e iniettare altri servizi in quelle classi lasciando la mia Order class pure.

Altra opzione

    public class Order{
      public Order(OrderId orderId, OrderStatus orderStatus, ICommandFactor orderCommandFactory){
        //assigned to local private fields.
      }

      public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;

      public void Cancel(){
        _orderCommandFactory.GetCancelCommand().Execute(this);
      }

      public void Success(){
        _orderCommandFactory.GetSuccessCommand().Execute(this);
      }

      internal void SetStatus(OrderStatus status){
        var oldStatus = new OrderStatus(_status);
        _status = status;
        if(OrderStatusChanged != null){
          OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
        }
      }
    }

L'approccio sopra descritto ha metodi ben definiti sulla classe Order ma per una separazione ideale delle preoccupazioni dovrò iniettare un factory di comandi, e ogni comando chiamerebbe il metodo internal SetOrderStatus come prima. I comandi senza essere iniettati in IOrderService verranno ora iniettati su Order .

Posso anche inserire un IOrderService nella classe Order al posto del factory di comando e la mia classe sarebbe la seguente:

    public class Order{
      public Order(OrderId orderId, OrderStatus orderStatus, IOrderService orderService){
        //assigned to local private fields.
      }

      public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;

      public void Cancel(){
        if(orderService.CanCancel(this)){
           SetStatus(OrderStatus.Cancel);
        }
      }

    public void Success(){
      if(orderService.IsSuccessful(this)){
          SetStatus(orderStatus.Success);
      }
    }

    internal void SetStatus(OrderStatus status){
      var oldStatus = new OrderStatus(_status);
      _status = status;
      if(OrderStatusChanged != null){
         OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
      }
    }
  }

Anche la mia classe Application avrà il seguente aspetto:

    public class OrderCancelApplicaiton{
      public void CancelPendingOrders(){
        var pendingOrders = _orderRepository.GetPendingOrders();
        foreach (var pendingOrder in pendingOrders)
        {
            pendingOrder.Cancel();
        }
      }
    }

Infine posso anche pensare di non avere alcun comando che controlli se un Order può essere cancellato, ma piuttosto un addizionale OrderStatusChanged gestore di eventi che controlla se il Order può effettivamente essere cancellato, se non lo muove tornando allo stato precedente, il problema con questo approccio è che l'oggetto 'Ordine' rimarrà in uno stato non valido anche se è in memoria e momentaneamente prima che venga riportato al suo stato precedente se non dovrebbe essere cancellato.

Sei comodo nell'iniettare Commands e Services nei tuoi modelli di dominio?
Cosa avresti fatto e qual è il metodo prescritto per tali problemi?
Preferiresti Cancel sull'oggetto dominio e permetti a un gestore di prenderlo e di "ripristinarlo" se non dovrebbe essere stato cancellato?

    
posta Vignesh.N 04.06.2017 - 00:31
fonte

2 risposte

1

"carica pochi ordini in sospeso dal DB e li cancella in base a pochi criteri, se questi criteri non corrispondono, non dovrei cancellarli"

Vorrei andare con un CancelOrderService simile al tuo primo esempio.

Come dici il vantaggio di questo su Order.Cancel è che puoi aggiungere servizi di business logic casuali senza modificare il codice esistente.

L'unica pecca del tuo esempio è che ritengo di ottenere più di un ordine da cancellare prima di passarci sopra.

Devi progettare i tuoi programmi Worker per essere in grado di funzionare contemporaneamente con se stessi e altri lavoratori che cambiano lo stato.

Significa bloccare gli ordini su cui stanno lavorando e bloccare il minor numero possibile alla volta.

    
risposta data 04.06.2017 - 11:34
fonte
1

Ehi, qualsiasi cosa funzioni per te. Qui ci sono un paio di osservazioni però.

Oggetto, verbo e oggetto

Una chiamata come myOrder.Cancel() potrebbe essere descritta in inglese come

An order is canceled.

Ho imparato che l'uso della voce passiva tende ad introdurre ambiguità, quindi passiamo a una voce attiva.

A user cancels an order.

Aspetta, non sarebbe myUser.Cancel(order) ?

Forse non importa ... oh aspetta, che dire di questi casi:

A CSR cancels an order

o

The system automatically cancels an expired order.

o

The system automatically cancels an order due to out of stock.

Sembra che questa operazione di annullamento possa coinvolgere altri oggetti e altre informazioni, come la ragione.

Quindi forse un'operazione unaria come myOrder.Cancel() non è sufficiente per soddisfare i tipi di requisiti che mi aspetto possa essere comune.

Sulla base di questo motivo, non penso che gli oggetti del modello di dominio debbano avere operazioni di questo tipo. Se hanno operazioni unarie, dovrebbero essere operazioni che si applicano solo al loro stato.

Coordinazione tra oggetti

Ora sembra che tu abbia un sistema di eventi intelligente in cui gli oggetti del modello possono aumentare e iscriversi agli eventi e tu gestisci il coordinamento in questo modo.

Personalmente preferisco avere un diverso livello di astrazione con il coordinamento tra oggetti. Il livello superiore chiamerà gli strati inferiori per aggiornare le variabili di stato.

Perché lo faccio? Bene, se hai solo uno strato in cui gli oggetti sono liberi di chiamarsi, puoi finire con chiamate circolari o ridondanti, e non è sempre chiaro chi debba fare cosa. Ma se ho uno strato separato, non c'è il rischio di chiamate circolari, perché il livello inferiore non può chiamare il livello superiore. Quindi la sequenza delle chiamate sembra un albero invece di un grafo non orientato.

Gli oggetti del dominio IMO devono operare su se stessi e gli oggetti aziendali o del flusso di lavoro devono essere utilizzati per coordinare le transazioni commerciali, che spesso includono più di un oggetto dominio.

    
risposta data 05.06.2017 - 23:31
fonte