Idee di progettazione: convalida basata su condizioni esterne al dominio

4

Sto affrontando un problema molto interessante qui, e mi piacerebbe vedere alcune idee di progettazione dal punto di vista del design guidato dal dominio.

Per facilitare l'espressione di ciò di cui ho bisogno, ho seguito una situazione ipotetica. Dispongo di un contesto limitato denominato Cliente , che viene utilizzato per gestire la creazione di nuovi clienti, l'aggiornamento del cliente, la ricerca dei clienti e così via. Ho un aspetto della classe del cliente

public class Customer
{
    public int Id { get; set ; }
    public int SocialInsuranceNumber { get; set ; }

    public bool IsValid()
    {
        // my logic to validate social insurance number
    }
}

Quindi ho una logica diretta nella mia entità per convalidare i dati. La regola di convalida del numero di previdenza sociale canadese indica che tutti i valori iniziano con 0 non devono essere utilizzati, ma il governo canadese potrebbe usarlo per qualche ragione. Quindi ora ci sono 2 regole di convalida.

L'azienda ha il requisito di una singola applicazione - per la maggior parte delle situazioni in cui l'utente inserisce un numero di assicurazione sociale dobbiamo assicurarci che il valore non possa iniziare con 0 e il valore deve essere un SIN valido, ma in qualche situazione unica dobbiamo consentire un SIN valido a partire da 0.

Il mio codice di servizio del dominio è simile a

public class CustomerService
{
    public void AddCustomer(Customer customer)
    {
        if (customer.IsValid())
        {
            // save to database
        }
    }
}

Se aggiungo un parametro booleano alla funzione Customer della classe IsValid() per fare apparire come IsValid(bool allowLeadingZero) , potrei far funzionare il codice. Ma penso che sembri brutto.

Dal punto di vista del design, come suggeriresti un modo più elegante per gestire tale requisito?

    
posta hardywang 13.03.2015 - 18:20
fonte

4 risposte

2

Se fossi in te vorrei rendere SocialInsuranceNumber un oggetto valore con convalida nel suo costruttore. Tuttavia, poiché la logica di convalida è complessa, potresti preferire delegare la logica di convalida in una Domain Factory.

Alla fabbrica verrà inserita una strategia di convalida in base al tipo di codice fiscale che stai convalidando.

Quindi il costruttore di SocialInsuranceNumber valuterà per NULL , vuoto, lunghezza minima, caratteri non consentiti, ... ecc., ma SocialInsuranceNumberFactory convaliderà se il modello utilizzato è valido nel caso in cui fosse canadese o non usando a CanadianSocialInsuranceNumberValidator o DefaultSocialInsuranceNumberValidator .

    
risposta data 20.04.2015 - 02:18
fonte
1
public class Customer 
{
    public int Id { get; set ; }
    public int SocialInsuranceNumber { get; set ; }

    public bool IsValid()
    {
        // my logic to validate social insurance number
    }
}

Per prima cosa, evita di creare oggetti non validi. Rende il codice più difficile da mantenere. Se il Cliente è un oggetto nel tuo modello di dominio, allora non dovrebbe fare la convalida dei dati - questo è un lavoro per il componente anti-corruzione da affrontare.

La solita risposta è creare un tipo di valore per il tuo stato.

public class Customer
{
    public Id<Customer> id
    public SocialInsuranceNumber sin
    ...
}

Il costruttore di questi tipi dovrebbe generare un'eccezione se i dati non sono validi; pertanto, l'entità cliente non deve preoccuparsi affatto di ciò.

The business has the requirement of one single application - for majority of the situations when user enters a social insurance number we have to make sure the value cannot start with 0 and the value must be a valid SIN, but in some unique situation we must allow a valid SIN starting with 0.

"Qualche situazione unica" è un po 'vaga; nel DDD dovresti sederti con i tuoi esperti di dominio e ottenere una migliore comprensione di ciò.

Primo problema: hai bisogno di supportare i clienti con SIN che usano lo 0 iniziale? Le uniche risposte accettabili sono "No, mai" e "Sì". No, non è mai semplice, quindi guardiamo sì.

Per prima cosa, creiamo un tipo di valore per le regole più lente del SIN.

public class ValidSocialInsuranceNumber {        ID stringa finale pubblico;

   ValidSocialInsuranceNumber(String id) {
      // throw illegal argument exception of the sin isn't valid
      validate(id);
      this.id = id;
   }

}

public class SocialInsuranceNumber {        public final ValidSocialInsuranceNumber validSin;

   SocialInsuranceNumber(ValidSocialInsuranceNumber validSin) {
       // throw an illegal argument exception if the sin
       // has been issued for Fictitious purposes
       forbidFictitious(validSin.id);
       this.validSin = validSin;
   }

}

Poiché a volte è necessario consentire un SIN valido ma fittizio, il cliente ha bisogno da modellare con il vincolo più lento.

public class Customer
{
    public Id<Customer> id
    public ValidSocialInsuranceNumber sin
    ...
}

Ma il tuo livello anti corruzione non è necessario; al contrario, la maggior parte dei percorsi può utilizzare la versione rigida per convalidare gli input dell'utente e quindi giù per passare all'altra quando il controllo non è più necessario.

addCustomer(String id, String sin) {
    customerId = new ID<Customer>(id);
    customerSIN = new SocialInsuranceNumber(sin);

    customer = new Customer(customerId, customerSIN.validSin)
}

Se il peccato non convalida secondo le rigide regole, riporta l'errore all'operatore umano, magari con una nota per fargli sapere che il supporto ha un percorso "univoco" che è possibile eseguire per conto dell'operatore .

Quindi, nel tuo percorso di codice "univoco", usa la logica più indulgente

addUniqueCustomer(String id, String sin) {
    customerId = new ID<Customer>(id);
    validSin = new ValidSocialInsuranceNumber(sin);

    customer = new Customer(customerId, validSin)
}

Ecco come lo gestirò nel caso come descritto, dove le vere eccezioni sono meno probabili degli errori di battitura.

Quando non è così, allora inizio a cercare di usare un processo; consente all'utente di essere creato (possibilmente in uno stato disabilitato), ma genera anche un evento che è stato aggiunto a un SIN insolito, emettendolo in modo che l'azienda possa agire. Utilizzare i process manager per mitigare le contingenze è un approccio comune in

    
risposta data 02.02.2016 - 01:15
fonte
0

Beh, non vedo alcun problema qui, in realtà.

Per considerare qualcosa come una regola di validazione dell'oggetto, devi essere in grado di prendere un oggetto e dirmi esattamente come convalidare questo oggetto per soddisfare la regola. Nel tuo caso non ci sono condizioni chiaramente definite quando applicare questa regola, quindi direi che non hai alcuna regola di validazione;)

...in some unique situation...

Quindi, fai un flag di tale situazione come parte dello stato dell'oggetto. E poi sarai in grado di applicare la regola di convalida su di esso.

    
risposta data 20.03.2015 - 13:29
fonte
0

Suggerisco di separare le entità Customer e SocialInsuranceNumber nelle proprie classi, in quanto entrambi contengono dati e hanno un comportamento:

  • Crea una classe SocialInsuranceNumber e sposta il metodo isValid () in questa classe come metodo statico isValid (int CandidateSIN) . Potrebbe essere una funzione membro, ma ciò significa che è possibile creare istanze non valide, che probabilmente non sarebbero saggi. Il costruttore SocialInsuranceNumber dovrebbe proibirlo.
  • Aggiungi un'istanza di SocialInsuranceNumber al tuo cliente. Compila questo solo se hai un SIN valido.
  • Per gestire il caso canadese, prendere in considerazione l'utilizzo di un metodo di consolidamento e convalida più generale come SocialInsuranceNumber (int SIN, String CountryCode) e isValid (int CandidateSIN, String CountryCode) . Quindi puoi seppellire le regole di convalida all'interno della classe SocialInsuranceNumber.
  • La classe Customer potrebbe avere un metodo isValid () , ma in futuro potrebbe essere necessario gestire (ad esempio) clienti deceduti o insolventi. Se hai bisogno di un metodo hasValidSIN () , devi solo verificare se il SIN non è nullo.

Un po 'fuori tema, ma il tipo int non è una buona scelta per un campo che è facoltativo. Ad esempio, i clienti esteri potrebbero non avere SIN.

    
risposta data 21.03.2015 - 00:01
fonte

Leggi altre domande sui tag