Repositórios DDD no aplicativo ou serviço de domínio

29

Atualmente, estou estudando o DDD e tenho algumas perguntas sobre como gerenciar repositórios com o DDD.

Na verdade, encontrei duas possibilidades:

Primeiro

A primeira maneira de gerenciar serviços que li é injetar um repositório e um modelo de domínio em um serviço de aplicativo.

Dessa forma, em um dos métodos de serviço de aplicativo, chamamos um método de serviço de domínio (verificando regras de negócios) e, se a condição for boa, o repositório será chamado em um método especial para persistir / recuperar a entidade do banco de dados.

Uma maneira simples de fazer isso pode ser:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

O segundo

A segunda possibilidade é injetar o repositório dentro do domainService e usar apenas o repositório através do serviço de domínio:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

A partir de agora, não sou capaz de distinguir qual é o melhor (se houver um) ou o que eles implicam no seu contexto.

Você pode me dar um exemplo onde um poderia ser melhor que o outro e por quê?

mfrachet
fonte
"injetar um repositório e um modelo de domínio em um serviço de aplicativo." O que você quer dizer com injetar um "modelo de domínio" em algum lugar? AFAICT em termos de modelo de domínio DDD significa todo o conjunto de conceitos do domínio e as interações entre eles que são relevantes para o aplicativo. É uma coisa abstrata, não é um objeto na memória. Você não pode injetar.
Alexey

Respostas:

31

A resposta curta é - você pode usar repositórios de um serviço de aplicativo ou de um serviço de domínio - mas é importante considerar por que e como você está fazendo isso.

Objetivo de um serviço de domínio

Os Serviços de Domínio devem encapsular conceitos / lógica de domínio - como tal, o método de serviço de domínio:

domainService.persist(data)

não pertence a um serviço de domínio, pois persistnão faz parte da linguagem onipresente e a operação de persistência não faz parte da lógica de negócios do domínio.

Geralmente, os serviços de domínio são úteis quando você possui regras / lógica de negócios que exigem coordenação ou trabalho com mais de um agregado. Se a lógica envolver apenas um agregado, ela deve estar em um método nas entidades desse agregado.

Repositórios no Application Services

Portanto, nesse sentido, no seu exemplo, prefiro a sua primeira opção - mas mesmo que haja espaço para melhorias, pois o serviço de domínio está aceitando dados brutos da API - por que o serviço de domínio deve saber sobre a estrutura de data?. Além disso, os dados parecem estar relacionados apenas a um único agregado, portanto, há um valor limitado no uso de um serviço de domínio para isso - geralmente eu colocaria a validação dentro do construtor da entidade. por exemplo

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

e lance uma exceção se for inválida. Dependendo da estrutura do aplicativo, pode ser simples ter um mecanismo consistente para capturar a exceção e mapeá-la para a resposta apropriada para o tipo de API - por exemplo, para uma API REST, retorne um código de status 400.

Repositórios nos Serviços de Domínio

Não obstante o acima, às vezes é útil injetar e usar um repositório em um serviço de domínio, mas apenas se seus repositórios forem implementados de forma que aceitem e retornem apenas raízes agregadas, e também onde você está abstraindo a lógica que envolve várias agregações. por exemplo

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

a implementação do serviço de domínio seria semelhante a:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

Conclusão

A chave aqui é que o serviço de domínio encapsula um processo que faz parte da linguagem onipresente. Para cumprir seu papel, ele precisa usar repositórios - e é perfeitamente bom fazê-lo.

Mas adicionar um serviço de domínio que agrupe um repositório com um método chamado persistagrega pouco valor.

Nessa base, se seu serviço de aplicativo estiver expressando um caso de uso que exige trabalhar apenas com um único agregado, não haverá problema em usar o repositório diretamente do serviço de aplicativo.

Chris Simon
fonte
Ok, então, se eu tenho regras de negócios (admitindo regras de Padrão de Especificação), se isso diz respeito apenas a uma entidade, devo apenas a validação nessa entidade? Parece estranho injetar regras de negócios, como controlar um bom formato de correio do usuário dentro da entidade do usuário. Não é? Em relação à resposta global, obrigado. Concluiu que não existe "regra padrão a ser aplicada" e depende realmente de nossos casos de uso. Eu tenho algum trabalho a fazer para bem distinguir tudo isso trabalho
mfrachet
2
Para esclarecer, as regras que pertencem à entidade são apenas as regras que são de responsabilidade dessa entidade. Concordo que controlar um bom formato de email de usuário não parece pertencer à entidade Usuário. Pessoalmente, gosto de colocar regras de validação como essa em um objeto de valor que representa um endereço de email. O usuário teria uma propriedade do tipo EmailAddress, e o construtor EmailAddress aceita uma string e lança uma exceção se a string não corresponder ao formato necessário. Em seguida, você pode reutilizar o EmailAddress ValueObject em outras entidades que precisam armazenar um endereço de email.
Chris
Ok, vejo por que usar o Value Object agora. Mas isso significa que o objeto de valor deve uma propriedade que seja a regra de negócios que gerencia o formato?
Mfrachet 9/09/16
11
Objetos de valor devem ser imutáveis. Geralmente, isso significa que você inicializa e valida no construtor e, para qualquer propriedade, usa o padrão público get / private set. Mas você pode usar construções de linguagem para definir igualdade, processo ToString, etc., por exemplo, kacper.gunia.me/ddd-building-blocks-in-php-value-object ou github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon
Obrigado, @ChrisSimon, finalmente e responda a uma situação DDD da vida real que envolve código e não apenas teoria. Passei 5 dias vasculhando a SO e a Web para obter um exemplo funcional de criação e salvamento de um agregado, e essa é a explicação mais clara que encontrei.
e_i_pi
2

Há um problema com a resposta aceita:

O modelo de domínio não pode depender do repositório e o serviço de domínio faz parte do modelo de domínio -> o serviço de domínio não deve depender do repositório.

Em vez disso, o que você deve fazer é montar todas as suas entidades necessárias para a execução da lógica de negócios já no serviço de aplicativo e apenas fornecer aos seus modelos objetos instanciados.

Com base no seu exemplo, pode ser assim:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

Portanto, regra geral: o modelo de domínio não depende das camadas externas

Aplicação vs serviço Domínio A partir deste artigo :

  • Os serviços de domínio são muito granulares, onde, como serviços de aplicativos, é uma fachada destinada a fornecer uma API.

  • Os serviços de domínio contêm lógica de domínio que naturalmente não pode ser colocada em uma entidade ou objeto de valor, enquanto os serviços de aplicativos orquestram a execução da lógica de domínio e não implementam eles próprios nenhuma lógica de domínio.

  • Os métodos de serviço de domínio podem ter outros elementos de domínio como operandos e retornar valores, enquanto os serviços de aplicativos operam com operandos triviais, como valores de identidade e estruturas de dados primitivas.

  • Os serviços de aplicativo declaram dependências nos serviços de infraestrutura necessários para executar a lógica do domínio.

SMS
fonte
1

Nenhum de seus padrões é bom, a menos que seus serviços e objetos encapsulem algum conjunto coerente de responsabilidade.

Primeiro, diga qual é o objeto do seu domínio e fale sobre o que ele pode fazer no idioma do domínio. Se pode ser válido ou inválido, por que não ter isso como uma propriedade do próprio objeto de domínio?

Se, por exemplo, a validade dos objetos só faz sentido em termos de outro objeto, talvez você tenha uma 'regra de validação X para objetos de domínio' que pode ser encapsulada em um conjunto de serviços.

A validação de um objeto requer armazená-lo dentro das regras de negócios? Provavelmente não. A responsabilidade de 'armazenar objetos' normalmente fica em um objeto de repositório separado.

Agora você tem uma operação que deseja executar que abrange uma gama de responsabilidades, cria um objeto, valida e, se válido, armazena.

Esta operação é intrínseca ao objeto do domínio? Em seguida, faça parte desse objeto, ou seja,ExamQuestion.Answer(string answer)

Isso se encaixa com alguma outra parte do seu domínio? coloque isso láBasket.Purchase(Order order)

Você prefere os serviços ADM REST? OK então.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Ewan
fonte