聚合必须只知道它自己的状态并使其行为基础?聚合可以在其行为(方法)中使用其他聚合的状态吗?

问题描述 投票:2回答:1

聚合可以在其行为(方法)中使用其他聚合的状态吗?我应该将链接注入其他聚合,可以访问其他聚合的服务。或者聚合必须只知道它自己的状态并使其行为基础?

DDD社区是否对此问题的“聚合”模式有清晰的认识?或者这个问题不适用于DDD?另一方面,例如,我听到了一个相当明确的社区观点,您不需要将注册表注入聚合。

先前的聚合意义的运作不应该达到服务水平(贫血模型),同时减少聚合的依赖性和连通性这一事实之间的界限在哪里?

如果聚合仅基于其状态并且没有外部依赖关系,那么是否有必要从域服务创建一个层?如何调用这些域名服务?

我有一个例子。我故意扔出所有额外的东西来尽可能地简化它。并且只留下一个操作。在这个例子中,我实现了这种方法:1。聚合是独立的,只在它自己的状态下满足不变量。 2.属于域的逻辑,但与其他聚合和服务交互被转移到域服务。

示例描述:这是有界的上下文 - 拍卖。这里有Lot,Bid,Bidder,Timer - 它们被放置或重新启动以计算胜利前的时间。这里考虑的操作是“出价”:

  1. 应用程序层从客户端获取所需数据,获取聚合,服务,存储库。比调用域服务(域服务BidMaker - > makeBid)并将所有必要的内容传递给该服务。
  2. 域名服务的方法(#Domain服务#BidMaker - > makeBid) а)聚合的调用方法(AR Lot - > makeBid),然后这个方法检查不变量,然后进行出价并将其添加到bidIds。 b)检查定时器是否存在于批次中,如果没有启动新定时器或使用域服务重新启动旧定时器 - WinnerTimer。 c)如果计时器是新的 - 使用存储库保存它们 d)聚合的调用方法(AR Lot - > restartTimers),然后此方法将计时器添加到winnerTimerId和winnerTimerBefore2HoursId。

我得到了域服务和聚合方法名称的重复。然而,从逻辑上讲,“出价”操作属于Lot聚合。但是,您需要将逻辑从BidMaker域服务转移到聚合,这意味着您还需要将计时器存储库和计时器服务注入聚合。

我想听听意见 - 在这个例子中你会改变什么以及为什么?还有关于第一个和主要问题的意见。谢谢大家。

/*Domain Service*/ BidMaker{
  void makeBid(
    WinnerTimer winnerTimer,
    TimerRepository timerRepository,
    int amount,
    Bidder bidder,
    Lot lot,
  ){
    //call lot.makeBid
    //check Lot's timers and put new or restart existing through WinnerTimer
    //save timers if they new through TimerRepository
    /call lot.restartTimers
  }
}

/*Domain Service*/ WinnerTimer{
  Timer putWinnerTimer(){}
  Timer putWinnerTimerBefore2Hours(){}
  void restartWinnerTimer(Timer timerForRestart){}
  void restartWinnerTimerBefore2Hours(Timer timerForRestart){}
}


/*AR*/ Lot{

  Lot{
    LotId id;
    BidId[] bidIds;
    TimerId winnerTimerId;
    TimerId winnerTimerBefore2HoursId;

    void makeBid(
      int amount,
      BidderId bidder,
      TimerId winnerTimerId,
      TimerId winnerTimerBefore2HoursId
    ){
      //check business invariants of #AR# within the boundaries
      //make Bid and add to bidIds
    }

    void restartTimers(TimerId winnerTimerId, TimerId winnerTimerBefore2Hours){
      //restart timers
    }
  }

  Bid{
    BidId id;
    int amount;
    BidderId bidderId;
  }

}

/*AR*/ Timer{
  Timer{
    TimerId id;
    Date: date;
  }
}

/*AR*/ Bidder{
  Bidder{
    BidderId: id;
  }
}

我的英语不好,对不起!

design-patterns architecture domain-driven-design modeling
1个回答
4
投票

聚合必须只知道自己的状态并以其行为为基础?

它自己的状态,以及当聚合被告知改变时传递的参数。

由于聚合描述了一致性边界,因此您不应该尝试将两个单独聚合的当前状态耦合在一起。但是使用另一个聚合的陈旧快照作为参数更新此聚合没有任何问题。

我通常不会使用聚合根本身;我认为一个有两个聚合根的方法令人困惑,因为哪一个被改变并不明显。但是聚合的只读视图没有这个问题。

更好的选择可能是使用无状态域服务作为中介。因此,改变Lot状态的方法永远不会改变Timer的状态,并且永远不会直接触及Timer,但是Lot可以询问有关Timer状态的问题,将无状态服务传递给定时器的id它关心。

你能给出论点,你为什么建议使用Aggregate中的域服务而不是聚合方法调用?

Tell, don't ask是支持将域服务传递给聚合的参数,并允许聚合决定传递给服务的ID以及在什么情况下。遵循该模式意味着保持描述聚合本身内聚合与计时器之间关系的逻辑,而不是将该逻辑分散到客户端代码中。

毕竟,分离域模型和应用程序的部分动机是您可以在一个地方维护所有域逻辑。

这种方法与the blue book中描述的模式一致。

一个反驳论点是你不再将域逻辑视为一个函数 - 它现在在它的中间具有这种读取效果。将效果保持在域逻辑之外是有好处的(您不需要模拟,因此它很容易推理并且易于测试)。

你如何实际使用它?

几乎就像它在锡上所说的那样 - 一般来说,它看起来像是两个存储库之间的编排;一个存储库提供您要修改的聚合,另一个存储库提供您将要传递给修改聚合的只读视图。存储库本身的实现只是管道。

关键的想法是,在我们只希望读取聚合(不改变其状态)的用例中,我们调用一个存储库方法,允许我们访问只读副本 - 而不是只有一个存储库调用所有用例(这不是从蓝皮书中获取的;这是从那以后学到的一课)。

© www.soinside.com 2019 - 2024. All rights reserved.