Exceções no DDD

11

Estou aprendendo DDD e estou pensando em lançar exceções em determinadas situações. Eu entendo que um objeto não pode entrar em um estado ruim; portanto, aqui as exceções são boas, mas em muitos exemplos as exceções também são lançadas, por exemplo, se estamos tentando adicionar um novo usuário com o email existente no banco de dados.

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()
        )
    );
}

Portanto, se o usuário com este email existir, no serviço de aplicativos podemos capturar uma exceção, mas não devemos controlar a operação do aplicativo usando o bloco try-catch.

Qual a melhor maneira para isso?

yadiv
fonte
1
Ter serviço de domínio (manipuladores) lançando exceções é bom.
Andy

Respostas:

20

Vamos começar com uma breve revisão do espaço do problema: Um dos princípios fundamentais do DDD é colocar as regras de negócios o mais próximo possível dos locais onde elas precisam ser aplicadas. Este é um conceito extremamente importante porque torna seu sistema mais "coeso". Mover regras "para cima" geralmente é um sinal de um modelo anêmico; onde os objetos são apenas sacos de dados e as regras são injetadas com esses dados a serem aplicados.

Um modelo anêmico pode fazer muito sentido para os desenvolvedores que estão começando com o DDD. Você cria um Usermodelo e um EmailMustBeUnqiueRuleque são injetados com as informações necessárias para validar o email. Simples. Elegante. A questão é que esse "tipo" de pensamento é de natureza fundamentalmente processual. Não DDD. O que acaba acontecendo é que você fica com um módulo com dezenas de Rulespacotes organizados e encapsulados, mas eles são completamente desprovidos de contexto ao ponto em que não podem mais ser alterados porque não está claro quando / onde eles são aplicados. Isso faz sentido? Ele pode ser auto-evidente que um EmailMustBeUnqiueRuleserá aplicado na criação de um User, mas o que dizer UserIsInGoodStandingRule?. Lenta mas seguramente, a granularização da extração doRulesFora do contexto deles, você fica com um sistema difícil de entender (e, portanto, não pode ser alterado). As regras devem ser encapsuladas somente quando a trituração / execução real é tão detalhada que seu modelo começa a perder o foco.

Agora, vamos à sua pergunta específica: o problema de ter o Service/ CommandHandlerthrow the Exceptioné que sua lógica de negócios está começando a vazar ("para cima") do seu domínio. Por que seu Service/ CommandHandlernecessidade de saber um e-mail deve ser único? A camada de serviço de aplicativo é geralmente usada para coordenação e não para implementação. A razão para isso pode ser ilustrada simplesmente se adicionarmos um ChangeEmailmétodo / comando ao seu sistema. Agora, os dois métodos / manipuladores de comando precisariam incluir sua verificação exclusiva. É aqui que um desenvolvedor pode ser tentado a "extrair" um EmailMustBeUniqueRule. Como explicado acima, não queremos seguir esse caminho.

Algumas informações adicionais podem nos levar a uma resposta mais DDD. A exclusividade de um email é uma invariável que deve ser aplicada em uma coleção de Userobjetos. Existe um conceito em seu domínio que represente uma "coleção de Userobjetos"? Eu acho que você provavelmente pode ver para onde estou indo aqui.

Para este caso específico (e muitos outros envolvendo a imposição de invariantes entre coleções), o melhor lugar para implementar essa lógica será o seu Repository. Isso é especialmente conveniente porque você Repositorytambém "conhece" a parte extra da infraestrutura necessária para executar esse tipo de validação (o armazenamento de dados). No seu caso, eu colocaria essa verificação no addmétodo Isso faz sentido, certo? Conceitualmente, é esse método que realmente adiciona um Userao seu sistema. O armazenamento de dados é um detalhe de implementação.

deslize do lado do rei
fonte
Eu poderia perguntar o que você quer dizer com isso Moving rules "upwards" is generally a sign of an anemic model? Para cima significa para a camada de aplicação? ou para o modelo de domínio?
Anyname Donotcare
1
"Para cima" significa para a camada de aplicativos (pense em uma arquitetura em camadas). Eu mencionei isso acima, mas a razão pela qual mover regras para cima é um sinal de um modelo anêmico é que ele representa a separação de dados e comportamento de uma maneira que derrota todo o propósito de ter um modelo de domínio principal. Se a validação de um email estiver em um módulo separado do que o envio, seu Emailmodelo deverá ser um conjunto de dados que são inspecionados por invariantes antes do envio. Veja: softwareengineering.stackexchange.com/questions/372338/…
king-side-slide
2
Mover a lógica do domínio para o repositório está incorreto. você deve aplicar a regra de domínio por raiz agregada na camada de domínio.
Arash
1
A validação do @Arash Set é complicada e geralmente não funciona bem com o DDD. Você pode optar por validar que algum processo funcionará antes de tentar esse processo (adicionando a User) ou aceitar que esse tipo específico de invariante não será devidamente encapsulado em seu domínio. O primeiro representa uma separação de dados e comportamento, resultando em um paradigma processual. Isso é menos que o ideal. A validação deve existir com dados, e não em torno de dados. Além disso, que benefício existe para criar um serviço de domínio que serve apenas para verificar esta regra? Aparentemente, este serviço ...
king-side-slide
1
Casais @Arash seu Repository, Userfaz, e isso invariantes e, portanto, não alcançar a visão purista você está empurrando para qualquer maneira. As implementações de repositório fazem parte do domínio e, portanto, podem receber determinadas responsabilidades.
king-side-slide
0

Você pode impor a validação em todas as camadas do seu aplicativo. Onde impor quais regras dependem do seu aplicativo.

Por exemplo, as entidades implementam métodos que representam regras de negócios, enquanto os casos de uso implementam regras específicas para seu aplicativo.

Se você estiver criando um serviço de e-mail como o Gmail, é possível argumentar que a regra "os usuários devem ter uma única e-mailadress" é uma regra comercial.

Se você estiver criando um processo de registro para um novo sistema de CRM, essa regra provavelmente será melhor implementada em um caso de uso, já que essa regra também fará parte do seu caso de uso. Além disso, também pode ser mais fácil testar, pois é possível stubar facilmente as chamadas do repositório nos testes de casos de uso.

Para segurança adicional, você também pode aplicar essa regra em seu repositório.

o novato
fonte