Domain-Driven-Design - dipendenze esterne nel problema Entity


23

Vorrei iniziare Domain-Driven-Design, ma ci sono diversi problemi che vorrei risolvere prima di iniziare :)

Immaginiamo di avere un gruppo e utenti e quando l'utente vuole unirsi a un gruppo, sto chiamando il groupsService.AddUserToGroup(group, user)metodo. In DDD dovrei farlo group.JoinUser(user), il che sembra abbastanza buono.

Il problema appare se ci sono alcune regole di convalida per l'aggiunta di un utente, o alcune attività esterne devono essere avviate quando l'utente viene aggiunto al gruppo. Avere questi compiti porterà all'entità con dipendenze esterne.

Un esempio potrebbe essere: una limitazione che l'utente può partecipare solo a 3 gruppi max. Ciò richiederà chiamate DB da dentro group.JoinUser metodo per convalidare questo.

Ma il fatto che un'entità dipenda da alcuni servizi / classi esterni non mi sembra così buono e "naturale" per me.

Qual è il modo corretto di gestirlo in DDD?

Risposte:


15

Immaginiamo di avere un gruppo e utenti e quando l'utente vuole unirsi a un gruppo, sto chiamando il metodo groupsService.AddUserToGroup (gruppo, utente). In DDD dovrei fare group.JoinUser (utente), che sembra piuttosto buono.

DDD ti incoraggia inoltre a utilizzare i servizi (senza stato) per eseguire attività, se l'attività a portata di mano è troppo complessa o non si adatta a un modello di entità. Va bene avere servizi nel livello di dominio. Ma i servizi nel livello di dominio dovrebbero includere solo la logica aziendale. Le attività esterne e la logica dell'applicazione (come l'invio di un'e-mail) d'altra parte, dovrebbero utilizzare il servizio di dominio nel livello dell'applicazione, in cui è possibile disporre di un servizio separato (applicazione) che lo avvolge, ad esempio.

Il problema appare se ci sono alcune regole di convalida per l'aggiunta di un utente ...

Le regole di validazione appartengono al modello di dominio! Dovrebbero essere incapsulati all'interno degli oggetti del dominio (entità ecc.).

... o alcune attività esterne devono essere avviate quando l'utente viene aggiunto al gruppo. Avere questi compiti porterà all'entità con dipendenze esterne.

Anche se non so di che tipo di attività esterna stai parlando, suppongo sia qualcosa come inviare un'e-mail, ecc. Ma questo non fa davvero parte del tuo modello di dominio. Dovrebbe vivere nel livello dell'applicazione e essere impilato lì imho. È possibile disporre di un servizio nel livello applicazione che opera su servizi e entità di dominio per eseguire tali attività.

Ma il fatto che un'entità dipenda da alcuni servizi / classi esterni non mi sembra così buono e "naturale" per me.

È innaturale e non dovrebbe accadere. L'entità non dovrebbe conoscere cose che non sono di sua responsabilità. I servizi dovrebbero essere utilizzati per orchestrare le interazioni tra entità.

Qual è il modo corretto di gestirlo in DDD?

Nel tuo caso, la relazione dovrebbe probabilmente essere bidirezionale. Se l'utente si unisce al gruppo o il gruppo accetta l'utente dipende dal tuo dominio. L'utente si unisce al gruppo? Oppure l'utente viene aggiunto a un gruppo? Come funziona nel tuo dominio?

Ad ogni modo, hai una relazione bidirezionale e puoi quindi determinare la quantità di gruppi a cui l'utente appartiene già all'interno dell'aggregazione utente. Se si passa l'utente al gruppo o il gruppo all'utente è tecnicamente banale una volta determinata la classe responsabile.

La convalida dovrebbe quindi essere eseguita dall'entità. Il tutto viene chiamato da un servizio del livello applicazione che può anche fare cose tecniche, come l'invio di e-mail ecc.

Tuttavia, se la logica di convalida è davvero complessa, un servizio di dominio potrebbe essere una soluzione migliore. In tal caso, incapsulare le regole aziendali e quindi chiamarle dal livello applicazione.


Ma se spostiamo così tanta logica fuori dall'entità, cosa dovrebbe essere tenuto dentro?
SiberianGuy

Le responsabilità dirette dell'entità! Se si può dire "l'utente può unirsi a un gruppo", ad esempio, è una responsabilità dell'entità utente. A volte devi prendere decisioni di compromesso per motivi tecnici. Non sono neanche un grande fan delle relazioni bidirezionali, ma a volte si adatta meglio al modello. Quindi ascolta attentamente quando parli del dominio. "Un'entità fa ..." "L'entità può ..." Quando senti queste frasi, allora quelle operazioni appartengono molto probabilmente all'entità.
Falcon,

Inoltre, sai che hai bisogno di un servizio quando due o più oggetti altrimenti non correlati devono partecipare a un'attività per realizzare qualcosa.
Falcon,

1
Grazie per la tua risposta, Falcon! A proposito, ho sempre cercato di utilizzare i servizi senza stato, quindi sono un passo avanti verso DDD :) Diciamo che in un dominio questa operazione UserJoinsToGroup appartiene al gruppo. Il problema è che per convalidare tale operazione devo sapere in quanti gruppi l'utente già partecipa (per negare un'operazione se è già> 3). Per sapere che devo interrogare il database. Come posso farlo dall'entità del gruppo? Ho altri esempi, quando ho bisogno di toccare il DB nelle operazioni che dovrebbero naturalmente appartenere all'entità (le posterò se necessario :))
Shaddix

2
Bene, se ci penso: che dire di un'entità GroupMembership? Può essere costruito da una fabbrica e questa fabbrica può accedere ai repository. Sarebbe un buon DDD e incapsula la creazione di appartenenze. La fabbrica può accedere ai repository, creare un abbonamento e quindi aggiungerlo rispettivamente all'utente e al gruppo. Questa nuova entità potrebbe anche incapsulare i privilegi. Forse è una buona idea.
Falcon,

3

Il modo in cui vorrei affrontare il problema di convalida è in questo modo: Creare un dominio del servizio denominato MembershipService:

class MembershipService : IMembershipService
{
   public MembershipService(IGroupRepository groupRepository)
   { 
     _groupRepository = groupRepository;
   }
   public int NumberOfGroupsAssignedTo(UserId userId)
   {
        return _groupsRepository.NumberOfGroupsAssignedTo(userId);
   }
}

L'entità del gruppo deve essere iniettata IMemberShipService. Questo può essere fatto a livello di classe o di metodo. Supponiamo che lo facciamo a livello di metodo.

class Group{

   public void JoinUser(User user, IMembershipService membershipService)
   {
       if(membershipService.NumberOfGroupsAssignedTo(user.UserId) >= 3)
         throw new BusinessException("User assigned to more than 3 groups. Cannot proceed");

       // do some more stuff
   }
}

Il servizio applicativo: GroupServicepuò essere iniettato IMemberShipServiceutilizzando l'iniezione Constructor, che può quindi passare al JoinUsermetodo della Groupclasse.


1
Potresti prendere in considerazione la formattazione del codice sorgente nel tuo post per leggibilità
Benni,
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.