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?
Respostas:
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
User
modelo e umEmailMustBeUnqiueRule
que 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 deRules
pacotes 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 umEmailMustBeUnqiueRule
será aplicado na criação de umUser
, mas o que dizerUserIsInGoodStandingRule
?. Lenta mas seguramente, a granularização da extração doRules
Fora 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
/CommandHandler
throw theException
é que sua lógica de negócios está começando a vazar ("para cima") do seu domínio. Por que seuService
/CommandHandler
necessidade 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 umChangeEmail
mé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" umEmailMustBeUniqueRule
. 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
User
objetos. Existe um conceito em seu domínio que represente uma "coleção deUser
objetos"? 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êRepository
també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 noadd
método Isso faz sentido, certo? Conceitualmente, é esse método que realmente adiciona umUser
ao seu sistema. O armazenamento de dados é um detalhe de implementação.fonte
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?Email
modelo deverá ser um conjunto de dados que são inspecionados por invariantes antes do envio. Veja: softwareengineering.stackexchange.com/questions/372338/…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 ...Repository
,User
faz, 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.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.
fonte