DDD: Criando Módulos Reutilizáveis ​​e Distinções de Tipo de Serviço (Domínio, Infraestrutura, Aplicativo)


8

Então, depois de ler "Implementando o design orientado a domínio por Vaughn Vernon", decidi refatorar meu código para obter melhor reutilização, isolando o que eu acredito serem os conceitos principais de domínio em módulos separados.

Cada módulo contém seu próprio conjunto de camadas arquiteturais distintas, que incluem as camadas Domínio, Infra-estrutura e Aplicativo / Apresentação (por recomendação de Vaughn, decidi separar ainda mais as responsabilidades da camada Aplicativo das rotas, controladores MVC + modelos existentes no diretório Camada de apresentação).

Decidi colocar cada uma dessas camadas dentro de seu próprio pacote; e cada pacote está referenciando a camada abaixo dela como uma dependência. ou seja: a camada de apresentação depende da camada de aplicativo, a aplicação depende de infraestrutura etc. Como o repositório faz parte do domínio, cada interface de repositório existe dentro da camada / pacote do domínio, sendo a implementação uma responsabilidade da camada / pacote de infraestrutura (Doctrine etc).

Espero que, ao reestruturar meu código dessa maneira, seja possível trocar a camada de aplicativos e reutilizar meu domínio em vários aplicativos da web.

Finalmente, o código parece que está começando a se moldar novamente, mas o que ainda me confunde é essa distinção entre aplicativos, infraestrutura e serviços de domínio.

Um exemplo comum de um serviço de domínio é algo que você usaria para senhas de hash. Isso faz sentido para mim do ponto de vista do SRP, pois a entidade Usuário não deve se preocupar com os muitos algoritmos de hash diferentes que podem ser usados ​​para armazenar as credenciais de um usuário.

Então, com isso em mente, tratei esse novo serviço de Domínio da mesma maneira que meus Repositórios; definindo uma interface no domínio e deixando a implementação na camada Infraestrutura. No entanto, agora estou pensando no que deve ser feito com os Serviços de Aplicativo.

Como está agora, cada Entidade possui seu próprio Serviço de Aplicativo, ou seja, a Entidade do Usuário possui um UserService na Camada de Aplicativo. O UserService nesse caso é responsável por analisar tipos de dados primitivos e manipular um caso de uso comum "UserService :: CreateUser (nome da string, email da string, etc): Usuário.

O que me preocupa é o fato de precisar reimplementar essa lógica em vários aplicativos, caso decida trocar a camada de aplicativos. Então eu acho que isso me leva às minhas próximas perguntas:

  1. Os Serviços de Domínio são apenas uma Interface que existe para fornecer uma camada de abstração entre a Camada de Infra-estrutura e seu Modelo? ou seja: Repositórios + Serviços de hash, etc.

  2. Eu mencionei ter um Serviço de Aplicativo que se parece com isso:

    • Acesso / Aplicativo / Serviços / UserService :: CreateUser (nome da string, email da string, etc): User

    • A assinatura do método aceita argumentos de tipo de dados primitivos e retorna uma nova Entidade do Usuário (não um DTO!).

    Isso pertence à camada Infraestrutura como uma implementação de alguma interface definida na camada Domínio ou a Camada de Aplicação é de fato mais apropriada devido a argumentos primitivos do tipo de dados, etc. ?

    exemplo:

    Access/Domain/Services/UserServiceInterface 

    e

    Access/Infrastructure/Services/UserService implements UserServiceInterface
  3. Como os módulos separados devem lidar com relacionamentos unidirecionais. O módulo A deve referir a camada de aplicação do módulo B (como está fazendo agora) ou a implementação da infraestrutura (via interface separada)?

  4. Os serviços de camada de aplicativo exigem uma interface separada? Se a resposta for sim, então onde eles devem estar localizados?


2
Essas são questões muito diversas. Eu sugiro que você divida isso em perguntas separadas.
precisa

Respostas:


7

Os Serviços de Domínio são apenas uma Interface que existe para fornecer uma camada de abstração entre a Camada de Infra-estrutura e seu Modelo? ou seja: Repositórios + Serviços de hash, etc.

As responsabilidades dos serviços de domínio incluem várias coisas. O mais óbvio é a lógica de moradia que não se encaixa em uma única entidade. Por exemplo, pode ser necessário autorizar um reembolso para uma determinada compra, mas para concluir a operação, você precisa de dados da Purchaseentidade, Customerentidade, CustomerMembershipentidade.

Os serviços de domínio também fornecem operações necessárias ao domínio para concluir sua funcionalidade, como PasswordEncryptionService, por exemplo , mas a implementação desse serviço residirá na camada de infraestrutura, pois será principalmente uma solução técnica.

Serviços de infra-estrutura são serviços que permitem uma operação de infraestrutura, como abrir uma conexão de rede, copiar arquivos do sistema de arquivos, conversar com um serviço da Web externo ou conversar com o banco de dados.

Serviços de aplicativo são a implementação de um caso de uso no aplicativo que você está construindo. Se você estiver cancelando uma reserva de voo, você:

  1. Consulte o banco de dados para o objeto Reserva.
  2. invocar Reserva-> cancelar.
  3. Salve o objeto no banco de dados.

A camada do aplicativo é o cliente do domínio. O domínio não tem idéia de qual é o seu caso de uso. Apenas expõe a funcionalidade por meio de seus agregados e serviços de domínio. A camada de aplicativo, no entanto, reflete o que você está tentando alcançar, orquestrando as camadas de domínio e infraestrutura.

Eu mencionei ter um Serviço de Aplicativo parecido com este: Access / Application / Services / UserService :: CreateUser (nome da string, email da string, etc): Usuário A assinatura do método aceita argumentos primitivos do tipo de dados e retorna uma nova Entidade do Usuário (não um DTO !).

O PHP pode não ser o melhor lugar para começar a aprender sobre DDD, pois muitas das estruturas PHP existentes no mercado (Laravel, Symfony, Zend, etc. etc) tendem a promover o RAD. Eles estão mais focados no CRUD e na tradução de formulários para entidades. CRUD! = DDD

Sua camada de apresentação deve ser responsável por ler as entradas do formulário do objeto de solicitação e chamar o serviço de aplicativo correto. O serviço de aplicativo criará o usuário e chamará o repositório do usuário para salvar o novo usuário. Opcionalmente, você pode retornar um DTO do usuário de volta à camada de apresentação.

Como os módulos separados devem lidar com relacionamentos unidirecionais. O módulo A deve referir a camada de aplicação do módulo B (como está fazendo agora) ou a implementação da infraestrutura (via interface separada)?

O módulo de palavras no idioma DDD tem um significado diferente do que você está descrevendo. Um módulo deve abrigar conceitos relacionados. Por exemplo, um módulo de pedido na camada de domínio pode abrigar os agregados de pedidos, a entidade OrderItem, OrderRepositoryInterface e MaxOrderValidationService.

Um módulo Order na camada do aplicativo pode abrigar OrderApplicationServie, CreateOrderCommand e OrderDto.

Se você está falando sobre camadas, cada camada deve preferencialmente depender das interfaces de outras camadas sempre que possível. A camada de apresentação deve depender das interfaces da camada de aplicação. A camada de aplicativo deve fazer referência às interfaces dos repositórios ou serviços de domínio.

Pessoalmente, não crio interfaces para entidades e objetos de valor porque acredito que as interfaces devem estar relacionadas a um comportamento, mas YMMV :)

Os serviços de camada de aplicativo exigem uma interface separada? Se a resposta for sim, então onde eles devem estar localizados?

Depende :) para aplicações complexas, construo interfaces porque aplicamos testes rigorosos de unidade, integração e aceitação. O acoplamento fraco é fundamental aqui e as interfaces estão na mesma camada (camada de aplicação).

Para aplicativos simples, eu construo contra os serviços de aplicativos diretamente.


Concordo com a maior parte do que você escreveu, apenas quero dizer que a parte RAD das estruturas ajuda muito a prototipar o aplicativo, para dissociar podemos confiar na Modelagem de Domínio, mesmo que o PHP não seja bem conhecido com isso, se ter uma implementação de registro ativa como camada de fonte de dados, podemos considerá-la DTO, como o tio bob disse que é outro tipo de DTO e um adaptador pode mediar entre a camada de domínio e os comandos da fonte de dados também são outro tipo de DTOs, a melhor prática é acoplar o DTO Forms to Command (DTO) não a entidade, quase toda estrutura possui contêiner DI, portanto, use interfaces.
Cherif BOUCHELAGHEM

1

Hmm, resposta curta a uma pergunta longa, mas vejo o padrão da seguinte maneira

Regra 1: os objetos de domínio devem ter uma única raiz agregada

Regra 2: raízes agregadas não devem ser muito grandes, divida as coisas em contextos limitados

Problema: As raízes agregadas logo se tornam muito grandes e não há uma maneira clara de traçar uma linha entre os vários modelos de domínio neles

Solução: Serviços de Domínio. Crie interfaces que você pode injetar nos modelos de domínio que lhes permitam fazer coisas fora do contexto de domínio ou raiz agregada.

Então, eu acho que diria que seus exemplos são apenas Serviços / Repositórios normais, etc, IDatabaseRepositoryForStoringUsers ou IGenericHashingCode

Um serviço de domínio permite a comunicação entre contextos limitados. ie

User.UpgradeAccount(IAccountService accountService)
{
    accountService.UpgradeUsersAccount(this.Id);
}

Onde Usuários e Contas estão em Raízes Agregadas / Contextos Limitados separados.

Se o usuário e a conta estiverem na mesma raiz agregada, é claro que você poderá:

User.UpgradeAccount()
{
    this.MyAccount.Upgrade();
}

Não estou totalmente claro da sua pergunta sobre como você está integrando o material nTier Application / Infrastructure e o módulo. Obvs, você realmente não deseja nenhuma referência cruzada entre contextos vinculados, portanto, você colocaria suas interfaces de serviço de domínio em seu próprio módulo, que não faz referência a nenhum outro contexto vinculado. limitando você a expor tipos de valor base ou apenas DTOs possíveis


A integração é uma preocupação do pacote / camada de aplicativos de cada módulo. ou seja: Ligações dinâmicas de contêiner de serviço + rotas. Quanto aos relacionamentos entre módulos - se outro módulo separado (ProjectManagement, por exemplo) precisar executar algum tipo de Autenticação em sua própria API, então eu incluo a camada de Aplicação do módulo "Acesso" como uma dependência listada. O gerenciador de pacotes recupera todas as dependências aninhadas restantes. Acesse Aplicativo -> Infraestruturas de Acesso -> Domínio de Acesso para que eu possa executar o trabalho necessário.
user2308097

1
Sim, acho que esse tipo de ligação entre domínios provavelmente é um erro. Você é provável começar dependências circulares
Ewan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.