2009-06-15 15 views
11

Sto lavorando al mio primo progetto DDD e penso di capire i ruoli di base delle entità, degli oggetti di accesso ai dati e della loro relazione. Ho un'implementazione di convalida di base che memorizza ogni regola di convalida con la sua entità associata. Funziona bene per regole che si applicano solo all'entità corrente, ma cade a pezzi quando sono necessari altri dati. Ad esempio, se ho la restrizione che un nome utente debba essere univoco, vorrei che la chiamata IsValid() restituisca false quando c'è un utente esistente con il nome corrente.Dove devo inserire un assegno univoco in DDD?

Tuttavia, non sto trovando alcun modo pulito per mantenere questa regola di validazione sull'entità stessa. Mi piacerebbe avere una funzione IsNameUnique sull'entità, ma la maggior parte delle soluzioni per farlo richiederebbe l'iniezione di un oggetto di accesso ai dati utente. Questa logica dovrebbe essere in un servizio esterno? Se è così, come faccio a mantenere la logica con l'entità stessa? O è qualcosa che dovrebbe essere al di fuori dell'entità utente?

Grazie!

risposta

2

In DDD, c'è un concetto chiamato aggregate. È fondamentalmente responsabile della coerenza all'interno dell'applicazione.

IMHO, in questo caso specifico, suppongo che il CustomerRepository si trovi all'interno di qualcosa come "Customer aggregate", essendo la classe Customer la radice aggregata.

La radice sarebbe quindi responsabile di eseguire tutte queste operazioni e nessun altro potrebbe accedere alle opzioni del CustomerRepository. E ci sono alcune possibilità:

  • Il CustomerRepository potrebbe generare un'eccezione se il nome non è univoco (e il cliente sarebbe catturare e restituire l'errore, o qualcosa del genere)
  • Il CustomerRepository potrebbe avere un IsValidUserName (), che il cliente avrebbe chiamato prima di fare qualsiasi altra cosa
  • Qualsiasi altra opzione che si può pensare di
3

mi piace la risposta di Samuel, ma per ragioni di semplicità, mi sento di raccomandare l'attuazione di un Specification. Si crea una specifica che restituisce un valore booleano per verificare se un oggetto soddisfa determinati criteri. Inietti un IUserRepository nella specifica, controlla se un utente esiste già con quel nome e restituisce un risultato booleano.

public interface ISpecification<T> 
{ 
    bool IsSatisfiedBy(TEntity entity); 
} 

public class UniqueUsernameSpecification : ISpecification<User> 
{ 
    private readonly IUserRepository _userRepository; 

    public UniqueUsernameSpecification(IUserRepository userRepository) 
    { 
    _userRepository = userRepository; 
    } 

    public bool IsSatisfiedBy(User user) 
    { 
    User foundUser = _userRepository.FindUserByUsername(user.Username); 
    return foundUser == null; 
    } 
} 

//App code  
User newUser; 

// ... registration stuff... 

var userRepository = new UserRepository(); 
var uniqueUserSpec = new UniqueUsernameSpecification(userRepository); 
if (uniqueUserSpec.IsSatisfiedBy(newUser)) 
{ 
    // proceed 
} 
+9

Non funziona. Altri thread possono inserire dati tra il controllo e il salvataggio. – dariol

0

Devo dire che questo è semplicemente al di là dell'ambito del DDD.

Quello che hai con DDD è un'aggregazione di eventi che producono un modello utile. Tuttavia, le relazioni tra i dati di tali modelli non sono necessariamente possibili.

Quale modello di coerenza stai utilizzando?

Se è possibile eseguire il commit di più eventi in una transazione ACID, è possibile garantire che le modifiche a un gruppo di aggregati avvengano in modo atomico.

Se si utilizza un modello di coerenza finale, potrebbe non essere in grado di convalidare queste informazioni in un secondo momento. E quando lo fai, potrebbe essere necessario compensare per le cose che hanno presumibilmente successo ma non sono più validi.

L'unicità deve essere risolta in un contesto. Se il modello è piccolo (in migliaia) è possibile avere un aggregato che rappresenta l'insieme di valori che si desidera essere univoci. Supponendo che una transazione aggregata di gruppo sia possibile.

In caso contrario, è sufficiente proiettare il modello in un database che supporta un vincolo di unicità. Se questa proiezione fallisce devi tornare al tuo aggregato e in qualche modo contrassegnarlo come non valido. Per tutto il tempo devi considerare il fallimento. È qui che un processo distribuito di lunga durata, come lo schema della saga, può essere utile ma richiede anche di pensare a cose aggiuntive.

In sintesi, se non è possibile utilizzare un modello di archiviazione con una consistenza forte, tutto diventa lotto più complicato. Detto questo, ci sono buoni, ben oltre i modelli per la gestione dei fallimenti in un ambiente distribuito, ma si gira un po 'il problema sulla testa perché ora è necessario considerare il fallimento, in ogni punto lungo il percorso, che è una buona cosa ma richiederà un investimento più lungo di tempo.

Problemi correlati