Ti suggerisco di provare a modellare le azioni a cui stai alludendo sopra nel tuo livello applicazione. Vale a dire RegisterAccount
e Login
, perché penso che possa aiutare a chiarire la situazione. Cioè, lavorando all'indietro dalla superficie pubblica che vorresti che il tuo dominio esponesse al tuo livello applicativo, può aiutarci a informarci sulle dovute responsabilità (e non dovresti ).
Ad esempio, potresti avere un gestore di comandi RegisterAccount
che si riduce al seguente:
accountRepository.Add( new Account(cmd.UserName, cmd.Password) )
accountRepository.Save() // throws DuplicateUserName
e un gestore di comandi Login
che si riduce a:
account = accountRepository.FindByUserName( cmd.UserName ) // throws NotFound
account.Login( cmd.Password ) // throws InvalidPassword
Quanto sopra rappresenta il modo più indicativo per modellare ciascun processo. Questa è una buona cosa. È importante che la tua applicazione possa presentare una "visione" chiara e concisa di ogni processo aziendale in termini di comportamento. Naturalmente, questo è esattamente ciò a cui si riferisce il livello dell'applicazione: coordinando il dominio con la minore logica possibile (ovvero le regole) in un modo per fornire questa vista. Pertanto, tenendo presente quanto sopra, passiamo alle tue domande specifiche.
Impostare la convalida è complicato. Se esiste una regola che stabilisce che UserName
deve essere univoco all'interno di un set di Account
, deve essere applicato dal set di Account
. Esiste un pezzo della tua applicazione che rappresenta un insieme di Account
? Spesso, la validazione impostata appartiene al Repository
. Ciò è particolarmente utile perché un Repository
"conosce" la singola infrastruttura critica necessaria per applicare questo tipo di invariante (l'archivio dati). Nel tuo caso (e molti altri come questo), ti consiglio di imporlo come vincolo nel tuo archivio dati e lasciare che AccountRepository
generi un'eccezione su Save
se viene aggiunto un Account
con un duplicato UserName
. Semplice. Dichiarativa.
Non posso consigliare di creare un servizio separato per verificare che l'aggiunta di Account
abbia esito positivo prima che venga aggiunta, poiché ciò rappresenta una separazione di dati e comportamento. L'idea di convalidare che alcuni processi avranno successo prima di tentare è un approccio fondamentalmente procedurale, non OOP, e certamente non DDD. Questa pratica può anche portare a tutti i tipi di duplicati e trucchi lungo la strada quando viene applicata liberamente a un sistema. Lascia che le regole esca con i dati, non intorno ai dati.
Proseguendo, possiamo vedere che la tua entità Account
richiede certamente una dipendenza da un meccanismo di coesistenza hashing, in quanto deve essere in grado di eseguire internamente l'hash e controllare gli hash per consentire sia la registrazione dell'account che i processi di accesso. La domanda è se questo dovrebbe esistere o meno come servizio di dominio o qualcos'altro. Sinceramente, non importa. I meccanismi interni di un'entità non sono importanti (da qui l'astrazione). Ciò che è importante è che ci concentriamo sul comportamento che vorremmo raggiungere e che i dati che supportano questo comportamento siano un dettaglio di implementazione. Questa è la prospettiva che DDD cerca di fornire.
Detto questo, capisco che l'hashing è una preoccupazione trasversale. A meno che il tuo algoritmo non sia specifico per il tuo dominio, sembrerebbe che l'hashing appartiene come parte dell'infrastruttura. Non lasciare che il tuo dominio si preoccupi di cose che non sono veramente regole di business (l'hashing è più un problema tecnico nella maggior parte dei settori).
Per quanto riguarda le fabbriche: una fabbrica dovrebbe essere utilizzata quando la creazione di un'entità è così dettagliata che il modello inizia a perdere la concentrazione. Soprattutto, NON quando vuoi incapsulare le regole. Decidere se una fabbrica dovrebbe essere impiegata dipende da voi.
EDIT : vorrei soffermarmi su un'altra idea che ho più o meno ballato nella mia risposta sopra, ma non ho esplicitamente affermato: l'idea di ritardare implementazione fino a quando assolutamente necessario. Questa può essere una tattica incredibilmente utile quando si progetta e si implementa un sistema. Nel libro Clean Architecture di Robert Martin, porta questa idea in particolare in relazione alle decisioni di alto livello come l'archiviazione dei dati, ma è utile anche nei microcosmi (come la modellazione di un flusso di lavoro).
Nel suo nucleo, significa spingere le regole (implementazione) in fondo alla strada il più lontano possibile. In termini del sistema di cui sopra, significa imporre un vincolo unico al livello più basso che può essere applicato o impegnarsi nell'hashing dove assolutamente necessario. Essere semplicemente consapevoli di questa pratica ti porterà a implementare naturalmente regole il più vicino possibile al comportamento di cui governano. Ovviamente, questo è il nucleo principale di OOP.