有相当一部分人使用CQRS来实现其域。我的感觉是,如果存储库的界面类似于它们使用的最佳实践,那么您就不会误入歧途。
根据我所见...
1)命令处理程序通常使用存储库通过存储库加载聚合。命令针对集合的单个特定实例;存储库通过ID加载根。我看不到有一种针对集合集合运行命令的情况(相反,您将首先运行查询以获取集合集合,然后枚举集合并对每个集合发出命令。
因此,在要修改聚合的情况下,我希望存储库返回实体(也就是聚合根)。
2)查询处理程序完全不涉及聚合;取而代之的是,它们使用聚合的投影进行工作-值对象,这些对象描述某个时间点聚合的状态。因此,以ProjectionDTO而不是AggregateDTO为例,您将拥有正确的想法。
在要针对聚合运行查询,准备进行显示等操作的情况下,我希望看到返回的是DTO或DTO集合,而不是实体。
您的所有getCustomerByProperty
来电对我来说都像是查询,因此它们属于后一类。我可能想使用一个入口点来生成集合,所以我想看看是否
getCustomersThatSatisfy(Specification spec)
是一个合理的选择;然后查询处理程序将根据给定的参数构造适当的规范,并将该规范传递到存储库。缺点是签名确实表明存储库是内存中的集合。我不清楚,如果存储库只是对关系数据库运行SQL语句的抽象,则谓词会为您带来很多好处。
不过,有些模式可以提供帮助。例如,与其手动构建规范,不如将约束的描述传递给存储库,并允许存储库的实现决定要做什么。
警告:检测到类似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);
}
}
总结:提供汇总和提供DTO之间的选择取决于您期望消费者如何使用它。我的猜测是支持每个上下文接口的一种具体实现。
GetCustomerByName('John Smith')
如果您的数据库中有二十个John Smiths,将返回什么?您似乎假设没有两个人具有相同的名字。