Hay una comunidad considerable de personas que usan CQRS para implementar sus dominios. Mi sensación es que, si la interfaz de su repositorio es análoga a las mejores prácticas utilizadas por ellos, no se desviará demasiado.
Basado en lo que he visto ...
1) Los manejadores de comandos usualmente usan el repositorio para cargar el agregado a través de un repositorio. Los comandos se dirigen a una única instancia específica del agregado; el repositorio carga la raíz por ID. No hay, por lo que puedo ver, un caso en el que los comandos se ejecuten contra una colección de agregados (en su lugar, primero ejecutaría una consulta para obtener la colección de agregados, luego enumeraría la colección y emitiría un comando para cada uno.
Por lo tanto, en contextos en los que va a modificar el agregado, esperaría que el repositorio devuelva la entidad (también conocida como raíz del agregado).
2) Los manejadores de consultas no tocan los agregados en absoluto; en cambio, trabajan con proyecciones de los agregados: objetos de valor que describen el estado de los agregados / agregados en algún momento. Entonces piense en ProjectionDTO, en lugar de AggregateDTO, y tiene la idea correcta.
En contextos en los que va a ejecutar consultas contra el agregado, prepararlo para su visualización, etc., esperaría ver un DTO, o una colección de DTO, devuelta, en lugar de una entidad.
Todas sus getCustomerByProperty
llamadas me parecen consultas, por lo que caerían en la última categoría. Probablemente quiera usar un único punto de entrada para generar la colección, por lo que estaría buscando para ver si
getCustomersThatSatisfy(Specification spec)
es una elección razonable; los manejadores de consultas luego construirían la especificación apropiada a partir de los parámetros dados y pasarían esa especificación al repositorio. La desventaja es que la firma realmente sugiere que el repositorio es una colección en memoria; No está claro para mí que el predicado te compre mucho si el repositorio es solo una abstracción de ejecutar una instrucción SQL en una base de datos relacional.
Sin embargo, hay algunos patrones que pueden ayudar. Por ejemplo, en lugar de construir la especificación a mano, pase al repositorio una descripción de las restricciones y permita que la implementación del repositorio decida qué hacer.
Advertencia: Java como mecanografía detectado
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 conclusión: la elección entre proporcionar un agregado y proporcionar un DTO depende de lo que espera que el consumidor haga con él. Mi conjetura sería una implementación concreta que soporte una interfaz para cada contexto.
GetCustomerByName('John Smith')
devolverá si tiene veinte John Smiths en su base de datos? Parece que estás asumiendo que no hay dos personas que tengan el mismo nombre.