2013-03-15 17 views
5

Ho una domanda sugli approcci di convalida in ddd. Ho letto opinioni piuttosto controverse su questo. Alcuni dicono che questo dovrebbe vivere fuori dall'entità, altri dicono che questo dovrebbe essere inserito nell'entità. Sto cercando di trovare un approccio che potrei seguire.Approccio di validazione in ddd

Ad esempio, supponiamo di avere l'entità Utente con e-mail e password. L'utente ha un metodo di registrazione (email, password). Dove deve essere inserita la convalida della posta elettronica e della password? La mia opinione personale è che dovrebbe essere all'interno del metodo Register(). Ma un simile approccio potrebbe portare a incasinare la classe User con elementi di validazione. Un approccio potrebbe essere quello di estrarre le regole di posta elettronica e password in oggetti di policy separati e comunque richiamarli dal metodo Register().

Qual è la tua opinione sugli approcci di convalida in DDD?

risposta

3

La validità effettiva dell'utente dipende dal contesto. L'utente può essere valido (nome e indirizzo e-mail sono validi), ma l'operazione di registrazione può essere impossibile da eseguire. Perché? Perché ci può essere già un utente con lo stesso nome. Quindi, l'utente che sembra valido in qualche contesto, può non essere valido nel contesto della registrazione.

Così ho spostato parte della convalida in entità o oggetti valore (ad esempio oggetto di posta) per evitare stati di entità ovviamente non validi (ad esempio utente con nome null). Ma la convalida dipendente dal contesto dovrebbe esistere in quel contesto. Quindi, se sto registrando utente:

Mail mail = new Mail(blahblahblah); // validates if blah is valid email 
User user = new User(name, mail); // validates if name is valid and mail not null 
// check if there already exist user with such name or email 
repository.Add(user); 

Inoltre penso che il metodo Register dovrebbe essere il metodo di qualche servizio di dominio (come MembershipService), perché sicuramente dovrebbe utilizzare alcuni repository.

+0

Register() inizializza semplicemente l'utente con dati quali password, e-mail, data di registrazione, stato di registrazione. Non chiamerà repository. Questa è la responsabilità di alcuni servizi di applicazioni di sicurezza. Ma questo non è il problema principale qui. Ho appena fornito un esempio di un'operazione di entità e ho cercato di analizzare dove collocare la convalida. – Markus

+0

@Markus Penso che il nome di questo metodo sia confuso :) –

5

Prima di tutto, penso che la convalida sia in qualche modo un argomento sfuggente dovuto in parte ai vari contexts all'interno dei quali deve essere considerato. In definitiva, ci saranno regole di validazione eseguite a vari livelli dell'applicazione. Per lo meno, dovrebbero esserci guardie standard negli oggetti del dominio. Questi sono solo regolari controlli preliminari e argomentativi che dovrebbero far parte di qualsiasi oggetto ben progettato e si adattano alla tua opinione per il metodo Reigster. Come affermato da lazyberezovsky, questo è per impedire che gli oggetti entrino in uno stato non valido. Mi schiero con la scuola sempre valida di se su questo. Penso che se è necessario mantenere le entità in uno stato non valido, è necessario creare una nuova entità per questo scopo.

Un problema con questo approccio, tuttavia, è che spesso è necessario esportare queste regole di convalida in altri livelli, ad esempio in un livello di presentazione. Inoltre, nel livello di presentazione, le regole devono essere formattate in modo diverso. Devono essere presentati tutti in una volta e potenzialmente tradotti in un'altra lingua, come JavaScript per il feedback immediato sul cliente. Può essere difficile o poco pratico tentare di estrarre le regole di convalida dalle eccezioni generate da una classe. In alternativa, le regole di convalida possono essere ricreate a livello di presentazione. Questo è molto più semplice e anche se potenzialmente violando DRY consente alle regole di essere dipendenti dal contesto. Un flusso di lavoro specifico può richiedere regole di convalida diverse rispetto a quelle imposte dall'entità stessa.

L'altro problema con l'approccio descritto è che possono esistere regole di convalida che esistono al di fuori dell'ambito di un'entità e che queste regole devono essere consolidate insieme alle altre regole. Ad esempio, per la registrazione degli utenti, un'altra regola è garantire che l'indirizzo di posta elettronica sia unico. Il servizio applicativo che ospita il caso d'uso applicabile generalmente applica questa regola. Tuttavia, deve anche essere in grado di esportare questa regola su altri livelli, come la presentazione.

Nel complesso, provo a inserire tanti controlli di vincoli nelle entità stesse perché penso che le entità dovrebbero essere sempre valide.A volte è possibile progettare la struttura delle regole in modo tale che possa essere utilizzata sia per aumentare le eccezioni sia per esporla ai livelli esterni. Altre volte è più semplice replicare semplicemente le regole tra i livelli.

2

Ad esempio, supponiamo di avere l'entità Utente con e-mail e password. L'utente ha un metodo di registrazione (email, password). Dove deve essere inserita la convalida della posta elettronica e della password?

Se stai seguendo DDD, l'indirizzo email e il nome utente sono concetti chiave nel tuo dominio, quindi devono essere rappresentati come entità. In tal caso, la convalida per un indirizzo di posta elettronica risiede nell'entità EmailAddress e la convalida per nome utente deve essere inserita nell'entità Username.

esempio pseudocodice:

class EmailAddress 
{ 
    Constructor(string email) 
    { 
     if (email.DoesNotMatchRegex("\[email protected]\w+.\w{2,3}")) 
      throw error "email address is not valid" 
    } 
} 

class Username 
{ 
    Constructor(string username) 
    { 
     if (username.length < 6) 
      throw error "username must be at least 6 characters in length" 
    } 
} 

class User 
{ 
    Register(Username username, EmailAddress email) 
    { 
     if (username == null) 
      throw error "user must have a username" 

     if (email == null) 
      throw new error "user must provide email address" 

     // At this point, we know for sure that the username and email address are valid... 
    } 
} 
+1

Posso confermare che questo approccio funziona molto bene. Vorrei solo aggiungere che [le eccezioni sono termini della lingua ubiquitaria] (http://epic.tesio.it/2013/03/04/exceptions-are-terms-ot-the-ubiquitous-language.html). –

+0

Non valgono oggetti nome utente e indirizzo email? –

+0

@Barthelomeus si sarebbero candidati eccellenti per oggetti di valore. Non esiste * necessariamente * una mappatura tra entità/oggetti valore e classi/strutture - una classe può ancora essere un oggetto valore, sebbene sia spesso utile fare le distinzioni lungo tali linee. – MattDavey

0

Prima di tutto vorrei separare tra due cose: la convalida e regole di business in cui quest'ultimo normalmente va in un servizio o un metodo di entità. Per la convalida, come un indirizzo e-mail valido o password valide, di solito li attacca all'entità che li possiede in modo da poterli spostare con l'entità se ho trasferito la mia applicazione su una piattaforma diversa. Ora per il tuo esempio di metodo di registrazione non riesco a vedere perché questo metodo dovrebbe essere chiamato in primo luogo se l'oggetto era in uno stato non valido, e se verrà chiamato lo farò un passo in un altro metodo come SignUp che potrebbe fare qualcosa di simile: if (isValid()) register(username, password).