Dove dovremmo mettere la validazione per il modello di dominio

37

Apprezzo ancora le migliori pratiche per la convalida del modello di dominio. Va bene inserire la validazione nel costruttore del modello di dominio? esempio di convalida del mio modello di dominio come segue:

public class Order
 {
    private readonly List<OrderLine> _lineItems;

    public virtual Customer Customer { get; private set; }
    public virtual DateTime OrderDate { get; private set; }
    public virtual decimal OrderTotal { get; private set; }

    public Order (Customer customer)
    {
        if (customer == null)
            throw new  ArgumentException("Customer name must be defined");

        Customer = customer;
        OrderDate = DateTime.Now;
        _lineItems = new List<LineItem>();
    }

    public void AddOderLine //....
    public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}


public class OrderLine
{
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal UnitPrice { get; set; }

    public OrderLine(Order order, int quantity, Product product)
    {
        if (order == null)
            throw new  ArgumentException("Order name must be defined");
        if (quantity <= 0)
            throw new  ArgumentException("Quantity must be greater than zero");
        if (product == null)
            throw new  ArgumentException("Product name must be defined");

        Order = order;
        Quantity = quantity;
        Product = product;
    }
}

Grazie per tutti i tuoi suggerimenti.

    
posta adisembiring 15.11.2011 - 03:40
fonte

4 risposte

44

C'è un articolo interessante di Martin Fowler su quell'argomento che evidenzia un aspetto che molte persone (me compreso) tendono a trascurare :

But one thing that I think constantly trips people up is when they think object validity on a context independent way such as an isValid method implies.

I think it's much more useful to think of validation as something that's bound to a context - typically an action that you want to do. Is this order valid to be filled, is this customer valid to check in to the hotel. So rather than have methods like isValid have methods like isValidForCheckIn.

Da ciò segue che il costruttore dovrebbe non fare la validazione, eccetto forse un controllo di sanità mentale molto basilare condiviso da tutti i contesti.

Sempre dall'articolo:

In About Face Alan Cooper advocated that we shouldn't let our ideas of valid states prevent a user from entering (and saving) incomplete information. I was reminded by this a few days ago when reading a draft of a book that Jimmy Nilsson is working on. He stated a principle that you should always be able to save an object, even if it has errors in it. While I'm not convinced that this should be an absolute rule, I do think people tend to prevent saving more than they ought. Thinking about the context for validation may help prevent that.

    
risposta data 15.11.2011 - 10:06
fonte
5

Nonostante questa domanda sia un po 'vecchia, vorrei aggiungere qualcosa di utile:

Mi piacerebbe essere d'accordo con @MichaelBorgwardt ed estenderlo aumentando la testabilità. In "Lavorare efficacemente con il codice legacy", Michael Feathers parla molto degli ostacoli ai test e uno di questi ostacoli è rappresentato da oggetti "difficili da costruire". Costruire un oggetto non valido dovrebbe essere possibile e, come suggerisce Fowler, i controlli di validità dipendenti dal contesto dovrebbero essere in grado di identificare tali condizioni. Se non riesci a capire come costruire un oggetto in un'imbracatura di test, avrai problemi a testare la tua classe.

Riguardo alla validità mi piace pensare ai sistemi di controllo. I sistemi di controllo funzionano analizzando costantemente lo stato di un'uscita e applicando un'azione correttiva quando l'uscita si discosta dal set point, questo viene chiamato controllo ad anello chiuso. Il controllo a ciclo chiuso si aspetta intrinsecamente le deviazioni e agisce per correggerle ed è così che funziona il mondo reale, motivo per cui tutti i sistemi di controllo reali utilizzano in genere controllori a circuito chiuso.

Penso che utilizzare la convalida dipendente dal contesto e gli oggetti facili da costruire renderà il sistema più semplice da utilizzare in futuro.

    
risposta data 21.02.2014 - 22:24
fonte
4

Come già sai già ...

In object-oriented programming, a constructor (sometimes shortened to ctor) in a class is a special type of subroutine called at the creation of an object. It prepares the new object for use, often accepting parameters which the constructor uses to set any member variables required when the object is first created. It is called a constructor because it constructs the values of data members of the class.

Il controllo della validità dei dati trasmessi come parametri c'tor è sicuramente valido nel costruttore - altrimenti potresti consentire la costruzione di un oggetto non valido.

Tuttavia (e questa è solo la mia opinione, non riesco a trovare alcun documento valido su di esso a questo punto) - se la convalida dei dati richiede operazioni complesse (come operazioni asincrone - forse convalida basata su server se si sviluppa un'app desktop), quindi è meglio inserire una funzione di inizializzazione o di convalida esplicita di qualche tipo e i membri sono impostati su valori predefiniti (come null ) in c'tor.

Inoltre, come nota a margine mentre lo includi nel tuo esempio di codice ...

A meno che tu non stia eseguendo ulteriori convalide (o altre funzionalità) in AddOrderLine , molto probabilmente esporrò List<LineItem> come una proprietà invece che Order funga da facade .

    
risposta data 15.11.2011 - 04:01
fonte
3

La convalida deve essere eseguita il prima possibile.

La convalida in qualsiasi contesto, con il modello di dominio o con qualsiasi altro modo di scrivere software, dovrebbe servire allo scopo di ciò che si desidera validare e al livello in cui ci si trova al momento.

In base alla tua domanda, suppongo che la risposta sarebbe dividere la convalida.

  1. La verifica della proprietà verifica se il valore per quella proprietà è corretto ad es. quando è previsto un intervallo tra 1-10.

  2. La convalida dell'oggetto garantisce che tutte le proprietà sull'oggetto siano valide insieme l'una con l'altra. per esempio. BeginDate è prima di EndDate. Supponiamo di leggere un valore dall'archivio dati e sia BeginDate che EndDate vengono inizializzati su DateTime.Min per impostazione predefinita. Quando si imposta BeginDate, non vi è alcun motivo per applicare la regola "deve essere prima di EndDate", poiché ciò non si applica ANCORA. Questa regola deve essere verificata DOPO che tutte le proprietà sono state impostate. Questo può essere chiamato al livello radice aggregato

  3. La convalida deve essere eseguita anche sull'entità aggregata (o radice aggregata). Un oggetto Order può contenere dati validi e così fa OrderLines. Ma poi una regola aziendale afferma che nessun ordine può essere superiore a $ 1.000. Come faresti applicare questa regola in alcuni casi questo è permesso. non puoi semplicemente aggiungere una proprietà "non convalidare l'importo" poiché ciò porterebbe ad abusi (prima o poi, forse anche tu, solo per ottenere questa "brutta richiesta" di mezzo).

  4. dopo c'è la convalida a livello di presentazione. Invierai davvero l'oggetto sulla rete, sapendo che fallirà? O risparmierai l'utente di questo burdon e lo informerai non appena avrà inserito un valore non valido. per esempio. il più delle volte il tuo ambiente DEV sarà più lento della produzione. Ti piacerebbe aspettare 30 secondi prima di essere informato di "hai dimenticato questo campo ANCORA durante un ALTRO giro di prova", specialmente quando c'è un bug di produzione da risolvere con il tuo capo che ti respira il collo?

  5. Si suppone che la convalida a livello di persistenza sia il più vicino possibile alla convalida del valore della proprietà. Ciò consentirà di evitare eccezioni con la lettura di errori "null" o "valore non valido" quando si utilizzano i programmi di mapping di qualsiasi tipo o semplici lettori di dati vecchi. L'uso di stored procedure risolve questo problema, ma richiede di scrivere nuovamente la stessa logica di valenza ed eseguirla di nuovo. E le stored procedure sono il dominio admin del DB, quindi non provare a fare il suo lavoro HIS (o peggio lo infastidisce con questo "picking nitty a cui non viene pagato".

quindi per dirlo con alcune parole famose "dipende", ma almeno ora sai perché dipende.

Vorrei poter mettere tutto questo in un unico posto, ma sfortunatamente, questo non può essere fatto. In questo modo si posizionerebbe una dipendenza da un "oggetto Dio" contenente TUTTE le convalide per TUTTI i livelli. Non vuoi percorrere quel sentiero oscuro.

Per questo motivo lancio solo eccezioni di validazione a livello di proprietà. Tutti gli altri livelli utilizzo ValidationResult con un metodo IsValid per raccogliere tutte le "regole non valide" e passarle all'utente in una singola AggregateException.

Durante la propagazione dello stack di chiamate, li raccolgo nuovamente in AggregateExceptions finché non raggiungo il livello di presentazione. Il livello di servizio può lanciare questa eccezione direttamente al client in caso di WCF come FaultException.

Questo mi consente di prendere l'eccezione e dividerlo per mostrare singoli errori ad ogni controllo di input o appiattirlo e mostrarlo in un unico elenco. La scelta è tua.

Questo è il motivo per cui ho anche menzionato la convalida della presentazione, per metterla in cortocircuito il più possibile.

Nel caso ti stia chiedendo perché ho anche la convalida a livello di aggregazione (o livello di servizio, se lo desideri), è perché non ho una sfera di cristallo che mi dice chi utilizzerà i miei servizi in futuro. Avrai abbastanza problemi a trovare i tuoi errori per impedire agli altri di fare errori li tuoi :) inserendo dati non validi.e.g. tu gestisci l'applicazione A, ma l'applicazione B invia alcuni dati usando il tuo servizio. Indovina chi chiedono prima quando c'è un insetto? L'amministratore dell'applicazione B comunicherà felicemente all'utente "non ci sono errori alla mia estremità, inserisco solo i dati".

    
risposta data 13.05.2017 - 21:56
fonte

Leggi altre domande sui tag