Specifiche Pattern Story presso Hepsiburada OMS

Jan 03 2023
Che cos'è il modello di specifica? Il modello di specifica ci consente di incapsulare una parte della conoscenza del dominio in una singola specifica di unità e riutilizzarla in diverse parti della base di codice. Secondo Martin Fowler ed Eric Evans, una specifica definisce un insieme di condizioni che un oggetto candidato deve soddisfare per soddisfare la specifica.
"Incapsula e semplifica la complessità alla base del sistema di gestione degli ordini"

Che cos'è il modello di specifica?

Il modello di specifica ci consente di incapsulare una parte della conoscenza del dominio in una singola specifica di unità e riutilizzarla in diverse parti della base di codice.

Secondo Martin Fowler ed Eric Evans , una specifica definisce un insieme di condizioni che un oggetto candidato deve soddisfare per soddisfare la specifica.

Da quale pericolo protegge il modello delle specifiche?

Il modello Spec è fondamentalmente per proteggerci da queste cose:

  • Duplicazione del codice (non ripeterti)
  • Pasticcio di regole aziendali complesse
  • Crescita inutilmente massiccia del codice

1-) Lo stile di vita degli oggetti di specifica si comporta come Transient , il che significa che verrà creata una nuova istanza del componente ogni volta che il servizio viene richiesto dal contenitore.

"Stile di vita dell'ambito transitorio"

2-) Le specifiche riguardano solo la soddisfazione dell'oggetto. Non piu !

“Guarda solo oggetto soddisfazione”

3-) Le specifiche non possono mai cambiare lo stato dell'oggetto che ne deriva.

"Pensieri oggetto in arrivo"

4-) Le specifiche non hanno il loro stato.

“Stato vuoto”

5-) Gli oggetti delle specifiche devono essere immutabili.

Domanda ?

Abbiamo detto "Le specifiche non possono mai cambiare lo stato dell'oggetto che ne deriva". Questa domanda viene dalle specifiche :) . Possiamo evitare che gli oggetti di dominio provenienti da specifiche non siano in grado di cambiare il loro stato?

Risposta:

Sì possiamo. Ad esempio In do diesis; Nel metodo inviamo e catturiamo l'oggetto da proteggere, - per maggiori dettagli - possiamo crearlo in questo modo:

private readonly IList<OrderLines> _orderLines.AsReadOnly();

Abbiamo uno stepper nel nostro OMS (Sistema di gestione degli ordini) relativo ai processi degli ordini. Il nostro stepper ha anche molti stati diversi con molte varianti e casi diversi, ma i nostri stati fondamentali sono Posizionato , In corso , In transito , Consegnato, Non consegnato, Annullato

“Modulo base dello stato Collocato e In corso”
"Modulo di base dello stato in transito e del caso di assistenza tecnica"

`Come puoi vedere alcuni esempi nelle immagini sopra, anche se sembra semplice, i nostri stati sono piuttosto dettagliati e abbiamo molte funzionalità.
Da questo :fire: "feature hell", non abbiamo potuto sviluppare senza errori, agile e molto veloce scrivendo il codice duplicato ovunque.`

Ecco perché è entrato in gioco lo Spec Pattern.

Come implementiamo il modello di specifica?

Esistono 2 casi d'uso principali per il modello di specifica:

  • Dati di selezione nel database : ricerca di record che corrispondono ai criteri di specificazione basati.
  • Convalida : quando è necessario verificare che vengano utilizzati solo oggetti adatti per un determinato scopo.

public interface ISpecification<in T>
{
    bool IsSatisfiedBy(T candidate, out Reason reason);
}

public class Reason
{
   public Reason(string detail,string businessExceptionCode, object exceptionData = null)
   {
       this.Detail = detail;
       this.BusinessExceptionCode = businessExceptionCode;
       this.ExceptionData = exceptionData;
    }
    public string Detail { get; }
    public string BusinessExceptionCode { get; }
    public object ExceptionData { get; set; }
 }

"Esempio di utilizzo della specifica composita"

abbiamo anche utilizzato specifiche composite per acquisire complesse regole di business unit.

public class AndSpecification<T> : ISpecification<T>
{
    private readonly ISpecification<T> rightCondition;
    private readonly ISpecification<T> leftCondition;

    public AndSpecification(ISpecification<T> leftCondition, ISpecification<T> rightCondition)
    {
        this.leftCondition = leftCondition;
        this.rightCondition = rightCondition;
    }

    public bool IsSatisfiedBy(T candidate, out Reason reason)
    {
        return leftCondition.IsSatisfiedBy(candidate, out reason) && rightCondition.IsSatisfiedBy(candidate, out reason);
    }

 }

public class OrSpecification<T> : ISpecification<T>
{
    private readonly ISpecification<T> leftCondition;
    private readonly ISpecification<T> rightCondition;

    public OrSpecification(ISpecification<T> leftCondition, ISpecification<T> rightCondition)
    {
        this.leftCondition = leftCondition;
        this.rightCondition = rightCondition;
     }
        
     public bool IsSatisfiedBy(T candidate, out Reason reason)
     {
        return leftCondition.IsSatisfiedBy(candidate,out reason) || rightCondition.IsSatisfiedBy(candidate, out reason);
     }

  }

Non resta che implementare l'interfaccia ISpecification e verificare se soddisfa i criteri pertinenti e utilizzarla ovunque e inoltre è possibile combinare diverse regole aziendali con specifiche composite.

"Ogni "specifica" della catena deve essere vera nelle specifiche composite"

Scenario di caso d'uso in tempo reale

Supponiamo di avere un microservizio di ordine che gestisce tutte le operazioni di ordine. Immaginiamo ora di inviare una richiesta di informazioni sull'ordine da questo endpoint "http://127.0.0.1/api/v1.0/orders/orderNumber/{orderNumber}" in base al numero dell'ordine. Il controller corrispondente chiamato GetOrderByOrderNumber Va al servizio OrderQueries come mostrato di seguito. Chiediamo se un indirizzo può essere modificato in questo servizio con l'aiuto delle specifiche.

public async Task<Order> GetByOrderNumber(string orderNumber)
  
    var order = await this.orderRepository.Find(orderNumber);

    if (order == null)
    {
      throw new KeyNotFoundException($"Order not found with orderNumber:{orderNumber}");
    }                 

    var orderLines = await this.orderLineRepository.FindByOrderNumber(orderNumber);
      
    var orderViewModel = new Order
    {
      OrderNumber = order.OrderNumber,
      User = this.mapper.Map<Order.User>(order.User),
      Tenant = order.Tenant,
      Completion = this.mapper.Map<Order.Completion>(order.Completion),
      IsAddressChangeAllowed = new ChangeAddressSpecification(orderLines).IsSatisfiedBy(order,out Reason reason),
      Version = order.Version,
      LastCommandOrigin = order.LastCommandOrigin,
      TagList = order.TagList,
   };

  return orderViewModel;
}

public class ChangeAddressSpecification : ISpecification<Order>
{
        private readonly List<OrderLines> _orderLines;

        public ChangeAddressSpecification(List<OrderLines> orderLines)
        {
            this._orderLines = orderLines;
        }

        public bool IsSatisfiedBy(Order candidate, out Reason reason)
        {
            var fraudCheck = new FraudCheckSpecification();
            return fraudCheck
                .And(new ShippingMethodIsHepsiJetSpecification(this._orderLines))
                .And(new CountryCodeIsTurkeySpecification())
                .And(new TenantIsHepsiburadaSpecification())
                .IsSatisfiedBy(candidate, out  reason);
        }
 }

public class CountryCodeIsTurkeySpecification : ISpecification<Order>
{
    public bool IsSatisfiedBy(Order candidate, out Reason reason)
    {
       reason = new Reason(string.Empty,ExceptionCodes.DefaultExceptionCode);
       if (candidate.CountryCode == CountryCodes.Turkey)
       {
           return true;
       }
       else
       {
           reason = new Reason("Country Code Is Not In Turkey", ExceptionCodes.DefaultExceptionCode);
           return false;
        }
      }
   }

Cosa abbiamo guadagnato da questa esperienza come OMS?

  • Non abbiamo dovuto ripetere la regola di dominio pertinente in punti diversi ogni volta che ne avevamo bisogno.
  • Le regole di dominio che acquistano importanza quando si uniscono, siamo in grado di incapsularle e utilizzarle da un unico punto. Esempio di ChangeAddressSpecification sopra.
  • Seguendo l'approccio linguistico onnipresente della progettazione guidata dal dominio, non solo uno sviluppatore di software ma anche il proprietario del prodotto può analizzare i criteri osservando il codice.
  • Abbiamo visto molteplici modelli di progettazione combinarsi in modo efficiente se attenti alle nostre esigenze.
  • Abbiamo molto; riusabilità , manutenibilità , accoppiamento libero delle regole di business dagli oggetti di business , leggibilità e facilità di test.

Caro lettore, grazie per aver letto dall'inizio alla fine. Puoi farmi domande e darmi un feedback dal mio indirizzo Linkedin . Buona codifica!!!