Eccezioni in DDD


11

Sto imparando DDD e sto pensando di lanciare eccezioni in determinate situazioni. Comprendo che un oggetto non può entrare in un cattivo stato, quindi qui le eccezioni vanno bene, ma in molti esempi le eccezioni vengono lanciate anche per esempio se stiamo provando ad aggiungere un nuovo utente con l'e-mail esistente nel database.

public function doIt(UserData $userData)
{
    $user = $this->userRepository->byEmail($userData->email());
    if ($user) {
        throw new UserAlreadyExistsException();
    }

    $this->userRepository->add(
        new User(
            $userData->email(),
            $userData->password()
        )
    );
}

Quindi, se esiste un utente con questa e-mail, nel servizio dell'applicazione possiamo rilevare un'eccezione, ma non dovremmo controllare il funzionamento dell'applicazione utilizzando il blocco try-catch.

Qual è il modo migliore per questo?


1
Avere il servizio di dominio (gestori) che genera eccezioni va bene.
Andy,

Risposte:


20

Cominciamo con una breve rassegna dello spazio problematico: uno dei principi fondamentali di DDD è quello di posizionare le regole aziendali il più vicino possibile ai luoghi in cui devono essere applicate. Questo è un concetto estremamente importante perché rende il tuo sistema più "coeso". Spostare le regole "verso l'alto" è generalmente un segno di un modello anemico; dove gli oggetti sono solo sacchi di dati e le regole vengono iniettate con quei dati da applicare.

Un modello anemico può avere molto senso per gli sviluppatori che iniziano con DDD. Si crea un Usermodello e un EmailMustBeUnqiueRuleche viene iniettato con le informazioni necessarie per convalidare l'e-mail. Semplice. Elegante. Il problema è che questo "tipo" di pensiero è fondamentalmente di natura procedurale. Non DDD. Ciò che finisce per succedere è che ti rimane un modulo con dozzine di Rulesordinatamente avvolti e incapsulati, ma sono completamente privi di contesto al punto in cui non possono più essere cambiati perché non è chiaro quando / dove vengono applicati. Ha senso? Esso può essere auto-evidente che un EmailMustBeUnqiueRuleverranno applicate sulla creazione di una User, ma per quanto riguarda UserIsInGoodStandingRule?. Lentamente ma sicuramente, la granularizzazione dell'estrazione diRulesfuori dal loro contesto ti lascia con un sistema che è difficile da capire (e quindi non può essere cambiato). Le regole dovrebbero essere incapsulate solo quando lo scricchiolio / esecuzione effettivi sono così dettagliati che il tuo modello inizia a perdere la concentrazione.

Passiamo ora alla tua domanda specifica: il problema di avere Service/ CommandHandlerbuttare Exceptionè che la tua logica aziendale sta iniziando a fuoriuscire ("verso l'alto") dal tuo dominio. Perché il tuo Service/ CommandHandlerbisogno di sapere che un'e-mail deve essere unica? Il livello di servizio dell'applicazione viene generalmente utilizzato per il coordinamento anziché per l'implementazione. La ragione di ciò può essere illustrata semplicemente se aggiungiamo un ChangeEmailmetodo / comando al tuo sistema. Ora ENTRAMBI i metodi / i gestori dei comandi dovrebbero includere il controllo univoco. È qui che uno sviluppatore può essere tentato di "estrarre" un EmailMustBeUniqueRule. Come spiegato sopra, non vogliamo seguire questa strada.

Qualche ulteriore approfondimento delle conoscenze può portarci a una risposta più DDD. L'unicità di un'e-mail è un invariante che deve essere applicato attraverso una raccolta di Useroggetti. Esiste un concetto nel tuo dominio che rappresenti una "raccolta di Useroggetti"? Penso che probabilmente puoi vedere dove sto andando qui.

Per questo caso particolare (e molti altri che coinvolgono l'applicazione di invarianti attraverso le raccolte), il posto migliore per implementare questa logica sarà nel vostro Repository. Ciò è particolarmente utile perché Repositoryanche "conosci" l'infrastruttura aggiuntiva necessaria per eseguire questo tipo di validazione (l'archivio dati). Nel tuo caso, inserirò questo controllo nel addmetodo. Questo ha senso vero? Concettualmente, è questo metodo che aggiunge veramente un Useral tuo sistema. L'archivio dati è un dettaglio di implementazione.


Posso chiederti cosa intendi Moving rules "upwards" is generally a sign of an anemic model? Verso l'alto significa per il livello applicazione? o al modello di dominio?
Anyname Donotcare,

1
"Verso l'alto" significa verso il tuo livello di applicazione (pensa a un'architettura a strati). Ho toccato questo punto sopra, ma il motivo per cui spostare le regole verso l'alto è un segno di un modello anemico è che rappresenta la separazione di dati e comportamento in un modo che sconfigge l'intero scopo di avere un modello di dominio di base. Se la convalida di un'e-mail si trova in un modulo separato rispetto all'invio di un'e-mail, il Emailmodello deve essere un sacchetto di dati che viene ispezionato per gli invarianti prima dell'invio. Vedi: softwareengineering.stackexchange.com/questions/372338/…
king-side-slide

2
Lo spostamento della logica di dominio nel repository è errato. dovresti imporre la regola del dominio dalla radice aggregata nel livello del dominio.
Arash,

1
La convalida di @Arash Set è complicata e spesso non funziona bene con DDD. Ti resta sia la scelta di convalidare che qualche processo funzionerà prima di tentare quel processo (aggiungendo un User), sia l'accettazione che questo specifico tipo di invariante non verrà incapsulato ordinatamente nel tuo dominio. Il primo rappresenta una separazione di dati e comportamento che si traduce in un paradigma procedurale. Questo è meno che ideale. La convalida dovrebbe esistere con i dati, non attorno ai dati. Inoltre, quali sono i vantaggi della creazione di un servizio di dominio che serve solo a controllare questa regola? Apparentemente, questo servizio ...
King-side-slide

1
@Arash accoppia il tuo Repository, Usere questo invariante e quindi non raggiunge la visione purista per cui stai spingendo comunque. Le implementazioni del repository fanno parte del dominio e pertanto possono essere assegnate determinate responsabilità.
King-side-slide

0

È possibile imporre la convalida in ogni livello dell'applicazione. Dove imporre quali regole dipendono dalla tua applicazione.

Ad esempio, le entità implementano metodi che rappresentano le regole aziendali mentre i casi d'uso implementano regole specifiche per l'applicazione.

Se si crea un servizio di posta elettronica come Gmail, si potrebbe sostenere che la regola "gli utenti dovrebbero avere un indirizzo di posta elettronica univoco" è una regola aziendale.

Se stai creando un processo di registrazione per un nuovo sistema CRM, è probabile che questa regola sia meglio implementata in un caso d'uso poiché è probabile che questa regola faccia parte del tuo caso d'uso. Inoltre, potrebbe anche essere più semplice testare poiché è possibile stub facilmente le chiamate al repository nei test del caso d'uso.

Per motivi di sicurezza adizionale, è possibile anche applicare questa regola nel proprio repository.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.