Il existe une importante communauté de personnes qui utilisent CQRS pour implémenter leurs domaines. Mon sentiment est que, si l'interface de votre référentiel est analogue aux meilleures pratiques utilisées par eux, vous ne vous égarerez pas trop.
D'après ce que j'ai vu ...
1) Les gestionnaires de commandes utilisent généralement le référentiel pour charger l'agrégat via un référentiel. Les commandes ciblent une seule instance spécifique de l'agrégat; le référentiel charge la racine par ID. Il n'y a pas, comme je peux le voir, un cas où les commandes sont exécutées sur une collection d'agrégats (à la place, vous devez d'abord exécuter une requête pour obtenir la collection d'agrégats, puis énumérer la collection et émettre une commande pour chacun.
Par conséquent, dans des contextes où vous allez modifier l'agrégat, je m'attendrais à ce que le référentiel retourne l'entité (aka la racine d'agrégat).
2) Les gestionnaires de requêtes ne touchent pas du tout aux agrégats; au lieu de cela, ils fonctionnent avec des projections des agrégats - objets de valeur qui décrivent l'état de l'agrégat / agrégats à un moment donné. Pensez donc à ProjectionDTO, plutôt qu'à AggregateDTO, et vous avez la bonne idée.
Dans des contextes où vous allez exécuter des requêtes sur l'agrégat, le préparer pour l'affichage, etc., je m'attends à voir un DTO, ou une collection DTO, renvoyé, plutôt qu'une entité.
Tous vos getCustomerByProperty
appels me ressemblent à des requêtes, donc ils entrent dans cette dernière catégorie. Je voudrais probablement utiliser un seul point d'entrée pour générer la collection, donc je chercherais à voir si
getCustomersThatSatisfy(Specification spec)
est un choix raisonnable; les gestionnaires de requêtes construiraient alors la spécification appropriée à partir des paramètres donnés et passeraient cette spécification au référentiel. L'inconvénient est que la signature suggère vraiment que le référentiel est une collection en mémoire; il n'est pas clair pour moi que le prédicat vous achète beaucoup si le référentiel n'est qu'une abstraction de l'exécution d'une instruction SQL sur une base de données relationnelle.
Cependant, il existe certains modèles qui peuvent aider. Par exemple, au lieu de construire la spécification à la main, transmettez au référentiel une description des contraintes et laissez l'implémentation du référentiel décider quoi faire.
Avertissement: Java comme la frappe détectée
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);
}
}
En conclusion: le choix entre fournir un agrégat et fournir un DTO dépend de ce que vous attendez du consommateur. Je suppose qu'une implémentation concrète prend en charge une interface pour chaque contexte.
GetCustomerByName('John Smith')
reviendra si vous avez vingt John Smith dans votre base de données? On dirait que vous supposez que deux personnes n'ont pas le même nom.