Es gibt eine beträchtliche Community von Leuten, die CQRS verwenden, um ihre Domänen zu implementieren. Ich bin der Meinung, dass Sie nicht zu weit in die Irre gehen werden, wenn die Schnittstelle Ihres Repositorys den von ihnen verwendeten Best Practices entspricht.
Basierend auf dem, was ich gesehen habe ...
1) Befehlshandler verwenden normalerweise das Repository, um das Aggregat über ein Repository zu laden. Befehle zielen auf eine einzelne bestimmte Instanz des Aggregats ab. Das Repository lädt den Stamm nach ID. Wie ich sehen kann, gibt es keinen Fall, in dem die Befehle für eine Sammlung von Aggregaten ausgeführt werden (stattdessen würden Sie zuerst eine Abfrage ausführen, um die Sammlung von Aggregaten abzurufen, dann die Sammlung aufzählen und jeweils einen Befehl ausgeben.
In Kontexten, in denen Sie das Aggregat ändern möchten, würde ich daher erwarten, dass das Repository die Entität (auch als Aggregatstamm bezeichnet) zurückgibt.
2) Abfragehandler berühren die Aggregate überhaupt nicht. Stattdessen arbeiten sie mit Projektionen der Aggregate - Wertobjekte, die den Status des Aggregats / der Aggregate zu einem bestimmten Zeitpunkt beschreiben. Denken Sie also eher an ProjectionDTO als an AggregateDTO, und Sie haben die richtige Idee.
In Kontexten, in denen Sie Abfragen für das Aggregat ausführen, es für die Anzeige vorbereiten usw., würde ich erwarten, dass ein DTO oder eine DTO-Sammlung anstelle einer Entität zurückgegeben wird.
Alle Ihre getCustomerByProperty
Anrufe sehen für mich wie Anfragen aus, sodass sie in die letztere Kategorie fallen würden. Ich würde wahrscheinlich einen einzelnen Einstiegspunkt verwenden wollen, um die Sammlung zu generieren, also würde ich nachsehen, ob
getCustomersThatSatisfy(Specification spec)
ist eine vernünftige Wahl; Die Abfragehandler würden dann die entsprechende Spezifikation aus den angegebenen Parametern erstellen und diese Spezifikation an das Repository übergeben. Der Nachteil ist, dass die Signatur wirklich darauf hindeutet, dass das Repository eine Sammlung im Speicher ist. Mir ist nicht klar, dass das Prädikat Ihnen viel kostet, wenn das Repository nur eine Abstraktion zum Ausführen einer SQL-Anweisung für eine relationale Datenbank ist.
Es gibt jedoch einige Muster, die helfen können. Anstatt die Spezifikation manuell zu erstellen, übergeben Sie beispielsweise eine Beschreibung der Einschränkungen an das Repository und lassen Sie die Implementierung des Repositorys entscheiden, was zu tun ist.
Warnung: Java-ähnliche Eingabe erkannt
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);
}
}
Fazit: Die Wahl zwischen der Bereitstellung eines Aggregats und der Bereitstellung eines DTO hängt davon ab, was Sie vom Verbraucher erwarten. Meine Vermutung wäre eine konkrete Implementierung, die eine Schnittstelle für jeden Kontext unterstützt.
GetCustomerByName('John Smith')
zurückgegeben, wenn Sie zwanzig John Smiths in Ihrer Datenbank haben? Es sieht so aus, als würden Sie davon ausgehen, dass keine zwei Personen denselben Namen haben.