当我需要调用某个业务方法时,我需要获得与操作相关的所有聚合根,即使操作与下面给出的操作一样原始(只是将项添加到集合中)。我错过了什么?或者是基于CRUD的方法,您可以在其中运行单个查询,包括表连接,最后选择和插入 - 数据库引擎为您完成所有工作 - 实际上在性能方面更好?
在下面的代码中,我需要查询单独的聚合根(它创建另一个数据库连接并发送另一个选择查询)。在实际应用程序中,我一直在查询多个单个聚合,一个商业操作最多可查询8个。如何提高性能/查询开销?
class Device
{
Set<ParameterId> parameters;
void AddParameter(Parameter parameter)
{
parameters.Add(parameter.Id);
}
}
class Parameter
{
ParameterId Id { get; }
}
class DeviceApplication
{
private DeviceRepository _deviceRepo;
private ParameterRepository _parameterRepo;
void AddParameterToDevice(string deviceId, string parameterId)
{
var aParameterId = new ParameterId(parameterId);
var aDeviceId = new DeviceId(deviceId);
var parameter = _parameterRepo.FindById(aParameterId);
if (parameter == null) throw;
var device = _deviceRepo.FindById(aDeviceId);
if (device == null) throw;
device.AddParameter(parameter);
_deviceRepo.Save(device);
}
}
我被告知你只能传递另一个聚合的ID:
class Device
{
void AddParameter(ParameterId parameterId)
{
parameters.Add(parameterId);
}
}
但IMO打破了封装(通过明确强调术语ID进入业务),它也不会阻止粘贴错误或不正确的身份(由用户创建)。
Vaughn Vernon给出了使用第一种方法(传递整个聚合实例)的应用程序服务的示例。
答案 0 :(得分:3)
简短的回答是 - 根本不查询您的聚合。
聚合是一种暴露行为而非数据的模型。通常,在聚合上使用getter被认为是代码气味(ID是例外)。这使得查询有点棘手。
从广义上讲,有两种相关的解决方法。可能有更多,但至少这些不打破封装。
选项1:使用域事件 - 通过让您的域(聚合根)发出事件来说明内部状态的更改,您可以在数据库中构建专门用于查询的表。如果做得好,您将拥有高性能,非规范化的可查询数据,必要时可以进行线性缩放。这使得查询非常简单。我在这篇博客文章中有一个例子:How to Build a Master-Details View when using CQRS and Event Sourcing
选项2:推断查询表 - 我是选项1的忠实粉丝,但是如果你没有事件源方法,你仍然需要在某些时候保持聚合状态。有各种各样的方法可以执行此操作,但您可以插入聚合的持久性管道,从而将可查询数据提取到读取模型中以供查询使用。
我希望这是有道理的。
答案 1 :(得分:0)
如果您发现在这种情况下使用带有连接的RDBMS查询可能会有效 - 可能您的聚合边界错误。
例如 - 为什么需要加载Parameter
才能将其添加到Device
?您已拥有此Parameter
的标识,您只需将此ID添加到Parameters
中的引用列表Device
即可。如果你这样做是为了满足你的ORM - 你最有可能做错了。
另请注意,您的聚合 是事务边界。您真的希望在一个事务和一个连接中完成所有数据库操作。