如何使用DDD高效查询多个聚合?

时间:2017-05-17 11:25:13

标签: repository domain-driven-design aggregateroot

当我需要调用某个业务方法时,我需要获得与操作相关的所有聚合根,即使操作与下面给出的操作一样原始(只是将项添加到集合中)。我错过了什么?或者是基于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给出了使用第一种方法(传递整个聚合实例)的应用程序服务的示例。

2 个答案:

答案 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 - 你最有可能做错了。

另请注意,您的聚合 是事务边界。您真的希望在一个事务和一个连接中完成所有数据库操作。