Design Orientado a Domínio - dependências externas no problema de Entidade

23

Gostaria de iniciar o Domain-Driven-Design, mas há vários problemas que gostaria de resolver antes de começar :)

Vamos imaginar que tenho grupos e usuários e, quando o usuário deseja ingressar em um grupo, estou chamando o groupsService.AddUserToGroup(group, user)método No DDD eu devo fazer group.JoinUser(user), o que parece muito bom.

O problema aparece se houver algumas regras de validação para adicionar um usuário ou se algumas tarefas externas precisarem ser iniciadas quando o usuário for adicionado ao grupo. A realização dessas tarefas levará a entidade a ter dependências externas.

Um exemplo poderia ser - uma restrição de que o usuário pode participar apenas de 3 grupos no máximo. Isso exigirá chamadas de banco de dados do método group.JoinUser para validar isso.

Mas o fato de uma Entidade depender de alguns serviços / classes externos não me parece tão bom e "natural".

Qual é a maneira correta de lidar com isso no DDD?

Shaddix
fonte

Respostas:

15

Vamos imaginar que tenho grupos e usuários e, quando o usuário deseja ingressar em um grupo, estou chamando o método groupsService.AddUserToGroup (group, user). No DDD eu deveria fazer group.JoinUser (user), que parece muito bom.

Mas o DDD também o incentiva a usar serviços (sem estado) para executar tarefas, se a tarefa em questão for muito complexa ou não se encaixar em um modelo de entidade. Não há problema em ter serviços na camada de domínio. Mas os serviços na camada de domínio devem incluir apenas lógica de negócios. As tarefas externas e a lógica do aplicativo (como o envio de um email), por outro lado, devem usar o serviço de domínio na camada do aplicativo, na qual você pode ter um serviço (aplicativo) separado, por exemplo.

O problema aparece se houver algumas regras de validação para adicionar um usuário ...

As regras de validação pertencem ao modelo de domínio! Eles devem ser encapsulados dentro dos objetos de domínio (entidades etc.).

... ou algumas tarefas externas precisam ser iniciadas quando o usuário é adicionado ao grupo. A realização dessas tarefas levará a entidade a ter dependências externas.

Embora eu não saiba de que tipo de tarefa externa você está falando, presumo que seja algo como enviar um e-mail etc. Mas isso não faz parte do seu modelo de domínio. Ele deve residir na camada de aplicação e ser mantido lá em imho. Você pode ter um serviço em sua camada de aplicativo que opera em serviços e entidades de domínio para executar essas tarefas.

Mas o fato de uma Entidade depender de alguns serviços / classes externos não me parece tão bom e "natural".

Não é natural e não deveria estar acontecendo. A entidade não deve saber sobre coisas que não são de sua responsabilidade. Os serviços devem ser usados ​​para orquestrar interações entre entidades.

Qual é a maneira correta de lidar com isso no DDD?

No seu caso, o relacionamento provavelmente deve ser bidirecional. Se o usuário ingressa no grupo ou o grupo leva o usuário depende do seu domínio. O usuário entra no grupo? Ou o usuário foi adicionado a um grupo? Como isso funciona no seu domínio?

De qualquer forma, você tem um relacionamento bidirecional e, portanto, pode determinar a quantidade de grupos aos quais o usuário já pertence no agregado de usuários. Se você passa o usuário ao grupo ou o grupo ao usuário é tecnicamente trivial depois de determinar a classe responsável.

A validação deve então ser realizada pela entidade. A coisa toda é chamada de um serviço da camada de aplicação que também pode fazer coisas técnicas, como enviar e-mails etc.

No entanto, se a lógica de validação for realmente complexa, um serviço de domínio pode ser uma solução melhor. Nesse caso, encapsule as regras de negócios e chame a partir da camada de aplicativos.

Falcão
fonte
Mas se movermos tanta lógica para fora da entidade, o que deve ser mantido dentro?
SiberianGuy
As responsabilidades diretas da entidade! Se você pode dizer "o usuário pode ingressar em um grupo", por exemplo, é uma responsabilidade da entidade do usuário. Às vezes, é necessário tomar decisões de troca por motivos técnicos. Também não sou muito fã de relacionamentos bidirecionais, mas às vezes ele se encaixa melhor no modelo. Portanto, ouça com atenção ao falar sobre o domínio. "Uma entidade faz ..." "A entidade pode ..." Quando você ouve essas frases, essas operações provavelmente pertencem à entidade.
Falcon
Além disso, você sabe que precisa de um serviço quando dois ou mais objetos não relacionados precisam participar de uma tarefa para realizar algo.
Falcon
1
Obrigado pela sua resposta, Falcon! Aliás, eu sempre tentei usar serviços sem estado, por isso estou um passo mais perto do DDD :) Digamos que em um domínio essa operação UserJoinsToGroup pertença ao Group. O problema é que, para validar essa operação, preciso saber em quantos grupos o usuário já participa (para negar uma operação, se já for> 3). Para saber que eu preciso consultar o banco de dados. Como posso fazer isso da entidade do grupo? Eu tenho mais alguns exemplos, quando eu preciso tocar a DB nas operações que deve, naturalmente, pertencem a entidade (eu vou publicá-las, se necessário :))
Shaddix
2
Bem, se eu pensar sobre isso: e uma entidade de associação ao grupo? Pode ser construído por uma fábrica e esta fábrica pode acessar os repositórios. Isso seria bom DDD e encapsula a criação de associações. A fábrica pode acessar Repositórios, criar uma Associação e depois adicioná-la ao usuário e ao grupo, respectivamente. Essa nova entidade também pode encapsular privilégios. Talvez seja uma boa ideia.
Falcon
3

A maneira como abordaria o problema da validação é assim: Crie um serviço de domínio chamado MembershipService:

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

A entidade do grupo precisa ser injetada IMemberShipService. Isso pode ser feito no nível da classe ou no método. Vamos supor que fazemos isso no nível do método.

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
   }
}

O serviço de aplicativo: GroupServicepode ser injetado IMemberShipServiceusando a injeção do Constructor, que pode ser transmitida para o JoinUsermétodo da Groupclasse.

Eklavya Gupta
fonte
1
Você pode querer considerar a formatação do código fonte no seu post para facilitar a leitura
Benni