聚合体必须知道它的行为并仅基于它自己的状态?聚合能否在其行为(方法)中使用其他聚合的状态?
The aggregate must know and base its behavior only on its own state? Can the aggregate use the state of other aggregates in its behavior (methods)?
聚合可以在其行为(方法)中使用其他聚合的状态吗?我是否应该注入指向其他聚合的链接、可以访问其他聚合的服务。或者聚合体必须知道它的行为并仅基于它自己的状态?
DDD社区对这个问题的"Aggregate"模式有清楚的认识吗?或者这个问题不适用于 DDD?另一方面,例如,我听到社区的一个相当明确的观点,即您不需要将存储库注入聚合。
前面聚合意义的运行不应该走到服务层面(贫血模型),同时减少聚合的依赖性和连通性,两者的界限在哪里?
如果聚合只是基于它的状态,没有外部依赖,那么有必要从领域服务做一个层吗?
如何调用这些域服务?
我有一个例子。我故意扔掉所有额外的内容以尽可能简化它。并且只留下了一个手术。
在这个例子中,我实现了这种方法:
1. 聚合体是独立的,只满足其自身状态的不变量。
2.属于域的逻辑,但与其他聚合和服务的交互转移到域服务中。
示例说明:
这就是限界上下文——拍卖。这里有 Lot、Bid、Bidder、Timer - 放置或重新启动以倒计时胜利前的时间。这里考虑的操作是"make bid":
- 应用层从客户端获取所需的数据,获取聚合、服务、存储库。比调用域服务(域服务 BidMaker -> makeBid)并将所有必要的传递给该服务。
域服务方法(#Domain Service#BidMaker -> makeBid)
а) 调用聚合方法 (AR Lot -> makeBid),然后此方法检查不变量,然后出价并将其添加到 bidIds。
b) 检查计时器是否存在于 lot 中,如果不存在则启动新计时器或使用域服务 - WinnerTimer 重新启动旧计时器。
c) 如果计时器是新的 - 使用存储库保存它们
d)调用聚合方法(AR Lot -> restartTimers),然后此方法将定时器添加到winnerTimerId è winnerTimerBefore2HoursId。
域服务和聚合的方法名称重复。然而,从逻辑上讲,"make a bid" 操作属于 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;
}
}
我的英语不好,抱歉!
the aggregate must know and base its behavior only on its own state?
它自己的状态,加上告诉聚合改变时传递的参数。
因为聚合描述了一致性边界,所以您不应该尝试将两个单独聚合的 当前 状态耦合在一起。但是使用另一个聚合的陈旧快照作为参数来更新这个聚合并没有错。
我通常不会使用聚合根本身;我认为具有两个聚合根的方法令人困惑,因为不清楚哪个正在更改。但是聚合的只读视图没有这个问题。
更好的选择可能是使用无状态域服务作为中介。因此,改变 Lot
状态的方法永远不会改变 Timer
的状态,也永远不会直接接触 Timer,但是 Lot
将能够询问有关 a 的状态的问题计时器,将它关心的计时器的 ID 传递给无状态服务。
Can you give arguments, why do you suggest to use domain service from Aggregate and not before to the aggregate method call?
Tell, don't ask 是支持将域服务传递给聚合的论据,并允许聚合决定在什么情况下将哪个 id 传递给服务。遵循该模式意味着将描述聚合和计时器之间关系的逻辑保留在聚合本身中,而不是让该逻辑分散在客户端代码中。
毕竟,分离域模型和应用程序的部分动机是您可以在一个地方维护所有域逻辑。
这是与 the blue book 中描述的模式一致的方法。
一个相反的论点是您不再将域逻辑视为一个函数——它现在在它的中间具有这种读取效果。将效果保持在域逻辑之外有很多好处(您不需要模拟,因此很容易推理和测试)。
How do you practically use it?
几乎和罐头上说的一模一样——总的来说,它看起来像是两个存储库之间的编排;一个存储库提供您要修改的聚合,另一个提供您要传递给修改后的聚合的只读视图。存储库本身的实现只是管道。
关键思想是,在我们只希望读取聚合(不更改其状态)的用例中,然后我们调用允许我们访问只读副本的存储库方法 - 而不是拥有为所有用例调用的单个存储库(这 不是 取自蓝皮书;这是多年来吸取的教训)。
聚合可以在其行为(方法)中使用其他聚合的状态吗?我是否应该注入指向其他聚合的链接、可以访问其他聚合的服务。或者聚合体必须知道它的行为并仅基于它自己的状态?
DDD社区对这个问题的"Aggregate"模式有清楚的认识吗?或者这个问题不适用于 DDD?另一方面,例如,我听到社区的一个相当明确的观点,即您不需要将存储库注入聚合。
前面聚合意义的运行不应该走到服务层面(贫血模型),同时减少聚合的依赖性和连通性,两者的界限在哪里?
如果聚合只是基于它的状态,没有外部依赖,那么有必要从领域服务做一个层吗? 如何调用这些域服务?
我有一个例子。我故意扔掉所有额外的内容以尽可能简化它。并且只留下了一个手术。 在这个例子中,我实现了这种方法: 1. 聚合体是独立的,只满足其自身状态的不变量。 2.属于域的逻辑,但与其他聚合和服务的交互转移到域服务中。
示例说明: 这就是限界上下文——拍卖。这里有 Lot、Bid、Bidder、Timer - 放置或重新启动以倒计时胜利前的时间。这里考虑的操作是"make bid":
- 应用层从客户端获取所需的数据,获取聚合、服务、存储库。比调用域服务(域服务 BidMaker -> makeBid)并将所有必要的传递给该服务。
域服务方法(#Domain Service#BidMaker -> makeBid)
а) 调用聚合方法 (AR Lot -> makeBid),然后此方法检查不变量,然后出价并将其添加到 bidIds。
b) 检查计时器是否存在于 lot 中,如果不存在则启动新计时器或使用域服务 - WinnerTimer 重新启动旧计时器。
c) 如果计时器是新的 - 使用存储库保存它们
d)调用聚合方法(AR Lot -> restartTimers),然后此方法将定时器添加到winnerTimerId è winnerTimerBefore2HoursId。
域服务和聚合的方法名称重复。然而,从逻辑上讲,"make a bid" 操作属于 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;
}
}
我的英语不好,抱歉!
the aggregate must know and base its behavior only on its own state?
它自己的状态,加上告诉聚合改变时传递的参数。
因为聚合描述了一致性边界,所以您不应该尝试将两个单独聚合的 当前 状态耦合在一起。但是使用另一个聚合的陈旧快照作为参数来更新这个聚合并没有错。
我通常不会使用聚合根本身;我认为具有两个聚合根的方法令人困惑,因为不清楚哪个正在更改。但是聚合的只读视图没有这个问题。
更好的选择可能是使用无状态域服务作为中介。因此,改变 Lot
状态的方法永远不会改变 Timer
的状态,也永远不会直接接触 Timer,但是 Lot
将能够询问有关 a 的状态的问题计时器,将它关心的计时器的 ID 传递给无状态服务。
Can you give arguments, why do you suggest to use domain service from Aggregate and not before to the aggregate method call?
Tell, don't ask 是支持将域服务传递给聚合的论据,并允许聚合决定在什么情况下将哪个 id 传递给服务。遵循该模式意味着将描述聚合和计时器之间关系的逻辑保留在聚合本身中,而不是让该逻辑分散在客户端代码中。
毕竟,分离域模型和应用程序的部分动机是您可以在一个地方维护所有域逻辑。
这是与 the blue book 中描述的模式一致的方法。
一个相反的论点是您不再将域逻辑视为一个函数——它现在在它的中间具有这种读取效果。将效果保持在域逻辑之外有很多好处(您不需要模拟,因此很容易推理和测试)。
How do you practically use it?
几乎和罐头上说的一模一样——总的来说,它看起来像是两个存储库之间的编排;一个存储库提供您要修改的聚合,另一个提供您要传递给修改后的聚合的只读视图。存储库本身的实现只是管道。
关键思想是,在我们只希望读取聚合(不更改其状态)的用例中,然后我们调用允许我们访问只读副本的存储库方法 - 而不是拥有为所有用例调用的单个存储库(这 不是 取自蓝皮书;这是多年来吸取的教训)。