C'è una considerevole comunità di persone che usano CQRS per implementare i loro domini. La mia sensazione è che, se l'interfaccia del tuo repository è analoga alle migliori pratiche utilizzate da loro, non andrai troppo fuori strada.
Basato su quello che ho visto ...
1) I gestori dei comandi di solito usano il repository per caricare l'aggregato tramite un repository. I comandi indirizzano una singola istanza specifica dell'aggregato; il repository carica la radice per ID. Non riesco a vedere un caso in cui i comandi vengono eseguiti su una raccolta di aggregati (invece, eseguiresti prima una query per ottenere la raccolta di aggregati, quindi enumerare la raccolta e inviare un comando a ciascuno di essi.
Pertanto, in contesti in cui modificherete l'aggregato, mi aspetterei che il repository restituisca l'entità (ovvero la radice aggregata).
2) I gestori di query non toccano affatto gli aggregati; invece, funzionano con le proiezioni degli aggregati - oggetti valore che descrivono lo stato dell'aggregato / aggregati in un determinato momento. Quindi pensa a ProjectionDTO, piuttosto che a AggregateDTO, e hai l'idea giusta.
In contesti in cui eseguirai query sull'aggregato, preparandolo per la visualizzazione e così via, mi aspetterei di visualizzare un DTO, o una raccolta DTO, anziché un'entità.
Tutte le tue getCustomerByProperty
chiamate mi sembrano domande, quindi rientrerebbero in quest'ultima categoria. Probabilmente vorrei usare un singolo punto di ingresso per generare la raccolta, quindi vorrei vedere se
getCustomersThatSatisfy(Specification spec)
è una scelta ragionevole; i gestori di query costruiscono quindi la specifica appropriata dai parametri forniti e passano tale specifica al repository. Il rovescio della medaglia è che la firma suggerisce davvero che il repository è una raccolta in memoria; non è chiaro per me che il predicato ti compra molto se il repository è solo un'astrazione dell'esecuzione di un'istruzione SQL su un database relazionale.
Ci sono alcuni modelli che possono aiutare, però. Ad esempio, invece di creare manualmente le specifiche, passare al repository una descrizione dei vincoli e consentire all'implementazione del repository di decidere cosa fare.
Attenzione: rilevata la digitazione come Java
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);
}
}
In conclusione: la scelta tra fornire un aggregato e fornire un DTO dipende da cosa ci si aspetta dal consumatore. La mia ipotesi sarebbe un'implementazione concreta che supporti un'interfaccia per ogni contesto.
GetCustomerByName('John Smith')
tornerà se hai venti John Smith nel tuo database? Sembra che tu stia supponendo che due persone non abbiano lo stesso nome.