no DDD, os repositórios devem expor uma entidade ou objetos de domínio?

11

Pelo que entendi, no DDD, é apropriado usar um padrão de repositório com uma raiz agregada. Minha pergunta é: devo retornar os dados como uma entidade ou objetos de domínio / DTO?

Talvez algum código explique mais a minha pergunta:

Entidade

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

Devo fazer algo assim?

public Customer GetCustomerByName(string name) { /*some code*/ }

Ou algo parecido com isto?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Pergunta adicional:

  1. Em um repositório, devo retornar IQueryable ou IEnumerable?
  2. Em um serviço ou repositório, eu deveria fazer algo como .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou apenas criar um método que seja algo parecido GetCustomerBy(Func<string, bool> predicate)?
codefish
fonte
O que GetCustomerByName('John Smith')retornará se você tiver vinte John Smiths em seu banco de dados? Parece que você está assumindo que não há duas pessoas com o mesmo nome.
Bdsl # 16/17

Respostas:

8

devo retornar os dados como uma entidade ou objetos de domínio / DTO

Bem, isso depende inteiramente dos seus casos de uso. A única razão pela qual consigo pensar em retornar um DTO em vez de uma entidade completa é se sua entidade é enorme e você só precisa trabalhar em um subconjunto dele.

Se for esse o caso, talvez você deva reconsiderar seu modelo de domínio e dividir sua grande entidade em entidades menores relacionadas.

  1. Em um repositório, devo retornar IQueryable ou IEnumerable?

Uma boa regra geral é sempre retornar o tipo mais simples (mais alto na hierarquia de herança) possível. Portanto, retorne, a IEnumerablemenos que você queira deixar o consumidor do repositório trabalhar com um IQueryable.

Pessoalmente, acho que retornar um IQueryableé uma abstração com vazamento, mas conheci vários desenvolvedores que argumentam apaixonadamente que não é. Na minha opinião, toda lógica de consulta deve ser contida e ocultada pelo Repositório. Se você permitir que o código de chamada personalize sua consulta, qual é o sentido do repositório?

  1. Em um serviço ou repositório, devo fazer algo como .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou apenas criar um método parecido com GetCustomerBy (predicado Func)?

Pela mesma razão que eu mencionei no ponto 1, definitivamente não use GetCustomerBy(Func<string, bool> predicate). Pode parecer tentador no começo, mas é exatamente por isso que as pessoas aprenderam a odiar repositórios genéricos. Está com vazamento.

Coisas como GetByPredicate(Func<T, bool> predicate)só são úteis quando estão escondidas atrás de aulas concretas. Portanto, se você tivesse uma classe base abstrata chamada RepositoryBase<T>cuja exposição protected T GetByPredicate(Func<T, bool> predicate)era usada apenas por repositórios concretos (por exemplo, public class CustomerRepository : RepositoryBase<Customer>), isso seria bom.

MetaFight
fonte
Então, você está dizendo, não há problema em ter classes de DTO na camada de domínio?
codefish
Não era isso que eu estava tentando dizer. Eu não sou um guru do DDD, então não posso dizer se isso é aceitável. Por curiosidade, por que você não retornou a entidade completa? É muito grande?
MetaFight
Oh não mesmo. Eu só quero saber o que é a coisa certa e aceitável a fazer. É para retornar uma entidade completa ou apenas o subconjunto dela. Eu acho que depende da sua resposta.
codefish
6

Há uma comunidade considerável de pessoas que usam o CQRS para implementar seus domínios. Meu sentimento é que, se a interface do seu repositório for análoga às melhores práticas usadas por eles, você não se extrapolará.

Com base no que eu vi ...

1) Os manipuladores de comando geralmente usam o repositório para carregar o agregado por meio de um repositório. Os comandos visam uma única instância específica do agregado; o repositório carrega a raiz por ID. Não há, pelo que vejo, um caso em que os comandos sejam executados em uma coleção de agregados (em vez disso, você executaria uma consulta para obter a coleção de agregados, depois enumerar a coleção e emitir um comando para cada um.

Portanto, em contextos em que você modificará o agregado, eu esperaria que o repositório retornasse a entidade (também conhecida como raiz agregada).

2) Os manipuladores de consulta não tocam nos agregados; em vez disso, eles trabalham com projeções dos objetos de valor agregado que descrevem o estado dos agregados / agregados em algum momento no tempo. Então, pense em ProjectionDTO, em vez de AggregateDTO, e você tem a idéia certa.

Nos contextos em que você executará consultas no agregado, prepará-lo para exibição e assim por diante, eu esperaria ver um DTO, ou uma coleção de DTO, retornada, em vez de uma entidade.

Todas as suas getCustomerByPropertychamadas parecem consultas para mim, para que se enquadram na última categoria. Eu provavelmente gostaria de usar um único ponto de entrada para gerar a coleção, portanto, estaria olhando para ver se

getCustomersThatSatisfy(Specification spec)

é uma escolha razoável; os manipuladores de consulta construiriam a especificação apropriada a partir dos parâmetros fornecidos e passariam essa especificação para o repositório. A desvantagem é que a assinatura realmente sugere que o repositório é uma coleção na memória; não está claro para mim que o predicado o comprará muito se o repositório for apenas uma abstração da execução de uma instrução SQL em um banco de dados relacional.

Existem alguns padrões que podem ajudar, no entanto. Por exemplo, em vez de construir a especificação manualmente, passe ao repositório uma descrição das restrições e permita que a implementação do repositório decida o que fazer.

Aviso: java como digitação detectada

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

Concluindo: a escolha entre fornecer um agregado e fornecer um DTO depende do que você espera que o consumidor faça com ele. Meu palpite seria uma implementação concreta suportando uma interface para cada contexto.

VoiceOfUnreason
fonte
Tudo isso é bom e bom, mas o solicitante não menciona o uso do CQRS. Você está certo, porém, o problema dele seria inexistente se ele o fizesse.
MetaFight 6/01/16
Não tenho conhecimento do CQRS, embora tenha ouvido falar sobre isso. Pelo que entendi, ao pensar no que retornar se o AggregateDTO ou o ProjectionDTO, retornarei o ProjectionDTO. Depois, getCustomersThatSatisfy(Specification spec)listarei as propriedades necessárias para as opções de pesquisa. Estou acertando?
codefish
Claro. Não acredito que o AggregateDTO exista. O objetivo de um agregado é garantir que todas as alterações satisfaçam os negócios invariáveis. É o encapsulamento das regras de domínio que precisam ser satisfeitas - ou seja, é comportamento. As projeções, por outro lado, são representações de alguns instantâneos do estado que eram aceitáveis ​​para os negócios. Veja editar para tentar esclarecer a especificação.
VoiceOfUnreason
Concordo com o VoiceOfUnreason de que um AggregateDTO não deveria existir - penso no ProjectionDTO como um modelo de exibição apenas para dados. Ele pode adaptar sua forma ao exigido pelo chamador, obtendo dados de várias fontes conforme necessário. A Raiz Agregada deve ser uma representação completa de todos os dados relacionados, para que todas as regras de referência possam ser testadas antes de salvar. Ele só muda de forma em circunstâncias mais drásticas, como alterações na tabela do banco de dados ou relacionamentos de dados modificados.
precisa
1

Com base no meu conhecimento de DDD, você deve ter isso,

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Em um repositório, devo retornar IQueryable ou IEnumerable?

Depende, você encontrará visões mistas de diferentes povos para esta pergunta. Mas, pessoalmente, acredito que, se o seu repositório for usado por um serviço como CustomerService, você poderá usar o IQueryable, caso contrário, permanecerá no IEnumerable.

Repositórios devem retornar IQueryable?

Em um serviço ou repositório, devo fazer algo como .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? ou apenas criar um método parecido com GetCustomerBy (predicado Func)?

No seu repositório, você deve ter uma função genérica, como você disse, GetCustomer(Func predicate)mas na sua camada de serviço, adicione três métodos diferentes, porque há uma chance de seu serviço ser chamado de clientes diferentes e eles precisarem de DTOs diferentes.

Você também pode usar o Generic Repository Pattern para CRUD comum, se ainda não estiver ciente.

Muhammad Raja
fonte