【问题标题】:May an aggregate use an aggregate lookup service or should the business logik be in a domain service?聚合可以使用聚合查找服务还是业务逻辑应该在域服务中?
【发布时间】:2019-08-25 13:32:38
【问题描述】:

我正在使用带有事件溯源的 CQRS 开发 DDD 软件。现在我想弄清楚我应该把我的业务逻辑放在哪里。

我有一个条件聚合根,它引用了一个 triggerId(触发器聚合根)。我还有一个 conditionGroup 聚合根,它包含一个 conditionIds 列表。而且我有一个看门狗聚合根,它包含一个 conditionGroupId。

当调用触发器聚合的 Create 方法时,域会发出 triggerReceived 事件。 triggerReceived 事件具有 triggerId 和 value 属性,该属性应通过与条件聚合中的 triggerId 链接的条件进行检查。

我在域端有一个订阅者,它监听这个事件。我的计划是检索订阅者中的所有看门狗并调用方法 watchdog.ShouldBark(triggerId, value)。然后看门狗需要查找 ConditionGroup 聚合(它具有 ConditionGroupId 作为属性)并调用 ConditionGroup.DoesGroupMatch(triggerId, value)。 ConditionGroup 必须查找所有 Condition 聚合(它有一个 conditionId 列表)并调用 Condition.DoesConditionMatch(triggerId, value) 方法。

因此,每个聚合都必须查找其他聚合以访问执行一些检查的业务逻辑方法(不进行更新)。

或者在域中拥有一些 watchdogService 并且看门狗服务执行所有聚合查找并具有业务逻辑是否更好?

所以我的问题是:聚合是否可以通过使用接口 (IAggregateStore) 进行业务逻辑检查来查找其他聚合(不更新,因为每个事件只应更新一个聚合)?

【问题讨论】:

  • 我看到事件源与 CQRS 一起工作的方式是,您可以获得在命令处理程序中处理命令所需的所有信息,并将所有信息传递给聚合以应用业务逻辑也涉及验证。在您的场景中,我无法清楚地了解为什么您有这么多聚合的上下文,在我看来,您需要在创建触发器时进行一些验证,我认为这可能发生在触发器聚合内不能发生?
  • 尽管您已经口头概述了方法调用,但要理解主要参与者及其联系还是有点困难。一些示例或伪代码会有所帮助,您可以指出该问题关注的特定代码段。

标签: c# domain-driven-design cqrs event-sourcing


【解决方案1】:

您的描述相当不完整,因此此答案也可能不完整。 根据您的描述 -

我在域端有一个订阅者,它监听这个事件。我的计划是检索订阅者中的所有看门狗并调用方法 watchdog.ShouldBark(triggerId, value)。

我可以在您的域设计中闻到一些非常糟糕的东西。 ShouldBark 方法的目的是什么。它必须更改您的 WatchDog 域的状态。如果是这样,你就走错了方向。

让我们先获取您的业务域。 触发器条件条件组看门狗

我有一个条件聚合根,它引用了一个 triggerId(触发器聚合根)。

所以,触发器聚合中的代码应该如下所示(您可能发现我的一些代码是伪代码)-

public class Trigger : AggregateRoot
{
    ...
    public void CreateTrigger(int value)
    {
        //You can also pass new guid for trigger from your service
        Guid triggerId = Guid.NewGuid();
        var @event = new TriggerReceived(triggerId, value);
        PublishEvent(@event);
    }
}

//this is your event
public class TriggerReceived
{
    public readonly int Value;
    public readonly Guid TriggerId;
    public TriggerReceived(Guid triggerId, int value)
    {
        Value = value;
        TriggerId = triggerId;
    }
}

现在进入正确的方向。将您的 Condition 服务订阅到此事件,而不是检索所有 watchDog。

public class ConditionService
{
    public void When(TriggerReceived @event)
    {
        //re-hydrate your condition aggregate root from event store
        var condition = getConditionByTriggerId(@event.TriggerId);
        //if you want to retrieve condition based on Value you can do that here
        //var condition = getConditionByValue(@event.Value);
        if(condition is not null)
            condition.MatchTrigger();
    }
}

然后在您的 Condition 聚合中 -

public class Condition : AggregateRoot
{
    ...
    public void MatchTrigger()
    {
        //your business logic here
        ...
        //we know trigger value matched one condition, so raise the next event
        publish(new TriggerConditionMatched(this));
    }
}

//this is your TriggerConditionMatched event
public class TriggerConditionMatched
{
    public readonly Condition Condition;
    public TriggerConditionMatched(Condition condition)
    {
        Condition = condition;
    }
}

基于此——

我还有一个 conditionGroup 聚合根,它包含一个 conditionIds 列表。

我们也可以说,每个条件都应该有一个名为ConditionGroupId的属性。订阅您的 ConditionGroup 服务以收听上述事件 -

public class ConditionGroupService
{
    public void When(TriggerConditionMatched @event)
    {
        //re-hydrate your condition group aggregate root from event store
        var conditionGroup = getConditionGroup(@event.Condition.GroupId);

        if(conditionGroup is not null)
            conditionGroup.MatchTriggerToConditionGroup();
    }
}


public class ConditionGroup : AggregateRoot
{
    ...
    public void MatchTriggerToConditionGroup()
    {
        ...
        //do some check here, your business logic

        //raise the next event
        publish(new TriggerConditionGroupMatched(this));
    }
}

//this is your TriggerConditionGroupMatched event
public class TriggerConditionGroupMatched
{
    public readonly ConditionGroup ConditionGroup;
    public TriggerConditionGroupMatched(ConditionGroup conditionGroup)
    {
        ConditionGroup = conditionGroup;
    }
}

最后,基于此——

我有一个看门狗聚合根,它包含一个 conditionGroupId。

您的 ConditionGroup 域中应该有一个 watchDogId 属性。所以,订阅你的 WatchDog 服务来监听 TriggerConditionGroupMatched 事件 -

public class WatchDogService
{
    public void When(TriggerConditionGroupMatched @event)
    {
        //re-hydrate your Watch Dog aggregate root from event store
        var watchDog = getWatchDog(@event.ConditionGroup.WatchDogId);

        if(watchDog is not null)
            watchDog.Bark();
    }
}

public class WatchDog : AggregateRoot
{
    ...
    public void Bark()
    {
        ...
        this.ShouldBark = true;

        //you should raise your next event
        publish(nextEvent);
    }
}

您可能已经发现这是一个由事件和订阅者组成的网络,但请记住,事件源系统应该始终保持一致。通过这种方式,您可以记录 TriggerConditionConditionGroupWatchDog 发生的所有事情。 p>

【讨论】:

  • 非常感谢您的详细回答。我没有想过这样做。这看起来确实是一种非常有前途的处理业务逻辑的方法,而不是我最初的计划。我会考虑并重构我的代码。
  • 我唯一的问题是我只能通过aggregateId从eventStore加载聚合。无法执行此表单 getConditionByTriggerId(triggerId) 中的所有查找。也许 TriggerAggregate 应该有一个 conditionIds 列表,反之亦然,而 ConditionAggregate 应该有一个 triggerIds 列表。在这种情况下,我可以根据 triggerId 查找 TriggerAggregate,获取所有 conditionId,然后可以从 eventStore 访问 ConditionAggregate。但是这个解决方案是干净的还是有其他方法可以解决这个问题?
  • TriggerCondition 之间是否存在 m-m 关系?在进一步解决与您的域相关的任何问题之前,我建议您阅读 Udi Dahan 关于 m-m relationship in DDD 的精彩帖子
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-30
  • 1970-01-01
  • 2021-05-30
  • 1970-01-01
  • 2017-05-11
  • 2018-09-13
  • 1970-01-01
相关资源
最近更新 更多