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 getCustomerByProperty
chamadas 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.
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.