DDD - Fabbrica o servizio?

0

Sono nuovo di DDD, e sono confuso all'inizio del mio progetto DDD. Per configurare un contesto, sto costruendo la parte di gestione degli utenti della mia app, quindi sto costruendo la mia entità Account, che contiene un Id, un nome utente e una password.

public class Account: IEntity<string>
{
    public string Id { get; }
    public string Username { get; }
    public string Password { get; }

    public Account(string id, string username, string password)
    {
        Id = id;
        Username = username;
        Password = password;
    }
}

L'interfaccia IEntity forza solo un tipo come id, come questo:

public interface IEntity<out T>
{
    T Id { get; }
}

Ora ecco il mio problema, quando creo l'account, roba come controllare che il nome utente sia univoco che richiede di colpire il repository e mi viene in mente la password, che utilizza un servizio di dominio IHashingPassword, quindi creare l'entità solo mettendo la logica in esso non sembra giusto.

Da quello che ho letto, la creazione di oggetti con complessità entra in una fabbrica, ma la creazione di questo oggetto non è abbastanza complessa, è solo che c'è qualche logica che non è controllabile dall'entità stessa (I ' Non sto iniettando il mio repository nella mia entità).

Quindi tendo a pensare che il controllo per il nome utente (repo) e l'hashing della password (servizio di hashing) dovrebbe essere fatto nel mio servizio di dominio invece che in fabbrica, o è corretto farlo nella mia fabbrica, come me fai ora:

public class AccountFactory: IAccountFactory
{
    private readonly IHashingService _hashingService;

    public AccountFactory(IHashingService hashingService)
    {
        _hashingService = hashingService;
    }

    public Entities.Account Create(string username, string password)
    {
        var id = IdentityBuilder.Build();
        var hashedPassword = _hashingService.HashPassword(password);

        return new Entities.Account(id, username, hashedPassword);
    }
}

Non sto ancora controllando il nome utente, è solo per mostrare la mia situazione attuale. Dovrebbe questo (il controllo del nome utente e della password di hashing) essere fatto in un servizio di dominio e dovrei graffiare la fabbrica, o dovrei farlo in fabbrica?

    
posta TanguyB 10.11.2018 - 19:57
fonte

1 risposta

1

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.

    
risposta data 16.11.2018 - 17:55
fonte

Leggi altre domande sui tag