Entity/Aggregate 中的 DDD 可重用功能
DDD reusable functionality in an Entity/Aggregate
我在 DDD 中有以下设计
Post 聚合为
- 正文:post
的HTML
横幅实体
- Html: 旗帜HTML
Banner实体属于Post聚合,所以我想在BodyWithBanners中创建一个方法BodyWithBanners =]Post合计。
此方法的要点是搜索 Post.Body 的 HTML 并插入 HTML 29=]横幅。
到目前为止,还不错。
但是我打算抽象地重用这个功能:"Insert some HTML inside another HTML"。所以我为此创建了一个不同的 class:BannerReplacer
问题来了,我应该如何调用这个新的class?
- 只需在 Post.BodyWithBanners 方法中创建一个实例(打破依赖注入)
- 在 Post 聚合的构造函数中传递 BannerReplacer(这可能是创建 Post 次)
- 将 BannerReplacer 传递给 BodyWithBanners 方法(这意味着客户端使用 Post 必须处理 BannerReplacer)
我暂时选择了第一个选项,但我觉得不太适应,我相信一定有更好的方法。
I have chosen for now the first option, but I don't feel really comfortable with it, I believe there must be a better way of doing this.
很多时候,第一个选项很好——所以你应该练习适应它。这主要意味着更多地考虑依赖注入的用途,并清楚地了解这些力量是否在这里发挥作用。
如果 Banner 是一个实体,在 domain-driven-design
意义上,那么它可能类似于内存中的状态机。它有一个它管理的数据结构,以及一些用于更改该数据结构或回答有关该数据结构的有趣问题的函数,但它没有 I/O、数据库、网络等问题。
这反过来又表明您可以 运行 在所有上下文中都以相同的方式进行 - 您不需要一堆替代实现来使其可测试。您只需实例化一个并调用其方法。
如果它运行在所有上下文中都是相同的方式,那么它不需要可配置的行为。如果你不需要能够配置行为,那么你就不需要依赖注入(因为这个实体的所有副本都将使用相同的依赖(副本)。
当您确实具有可配置的行为时,分析将需要查看范围。如果您需要能够将该行为从一次调用更改为下一次调用,那么调用者将需要了解它。如果行为改变的频率低于此频率,那么您可以开始研究 "constructor injection" 是否有意义。
您知道您打算对给定的方法调用使用单个 BannerReplacer,因此您可以立即从如下所示的方法开始:
class Banner {
void doTheThing(arg, bannerReplacer) {
/* do the bannerReplacer thing */
}
}
请注意,此签名完全不依赖于 bannerReplacer 的 lifetime。更具体地说,BannerReplacer 的生命周期可能比 Banner 更长,也可能更短。我们只关心生命周期是否比 doTheThing 方法长。
class Banner {
void doTheThing(arg) {
this.doTheThing(arg, new BannerReplacer())
}
// ...
}
在这里,调用者根本不需要知道BannerReplacer;我们每次都会使用默认实现的新副本。调用者关心使用哪个实现可以自己传递。
class Banner {
bannerReplacer = new BannerReplacer()
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
和以前一样的想法;我们只是使用了一个生命周期更长的 BannerReplacer 实例。
class Banner {
Banner() {
this(new BannerReplacer())
}
Banner(bannerReplacer) {
this.bannerReplacer = bannerReplacer;
}
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
与以前相同的想法,但现在我们允许 "injection" 可以比给定的 Banner 实例更长寿的默认实现。
从长远来看,通过分析了解当前问题的需求,可以选择合适的工具。
我在 DDD 中有以下设计
Post 聚合为
- 正文:post 的HTML
横幅实体
- Html: 旗帜HTML
Banner实体属于Post聚合,所以我想在BodyWithBanners中创建一个方法BodyWithBanners =]Post合计。
此方法的要点是搜索 Post.Body 的 HTML 并插入 HTML 29=]横幅。
到目前为止,还不错。
但是我打算抽象地重用这个功能:"Insert some HTML inside another HTML"。所以我为此创建了一个不同的 class:BannerReplacer
问题来了,我应该如何调用这个新的class?
- 只需在 Post.BodyWithBanners 方法中创建一个实例(打破依赖注入)
- 在 Post 聚合的构造函数中传递 BannerReplacer(这可能是创建 Post 次)
- 将 BannerReplacer 传递给 BodyWithBanners 方法(这意味着客户端使用 Post 必须处理 BannerReplacer)
我暂时选择了第一个选项,但我觉得不太适应,我相信一定有更好的方法。
I have chosen for now the first option, but I don't feel really comfortable with it, I believe there must be a better way of doing this.
很多时候,第一个选项很好——所以你应该练习适应它。这主要意味着更多地考虑依赖注入的用途,并清楚地了解这些力量是否在这里发挥作用。
如果 Banner 是一个实体,在 domain-driven-design
意义上,那么它可能类似于内存中的状态机。它有一个它管理的数据结构,以及一些用于更改该数据结构或回答有关该数据结构的有趣问题的函数,但它没有 I/O、数据库、网络等问题。
这反过来又表明您可以 运行 在所有上下文中都以相同的方式进行 - 您不需要一堆替代实现来使其可测试。您只需实例化一个并调用其方法。
如果它运行在所有上下文中都是相同的方式,那么它不需要可配置的行为。如果你不需要能够配置行为,那么你就不需要依赖注入(因为这个实体的所有副本都将使用相同的依赖(副本)。
当您确实具有可配置的行为时,分析将需要查看范围。如果您需要能够将该行为从一次调用更改为下一次调用,那么调用者将需要了解它。如果行为改变的频率低于此频率,那么您可以开始研究 "constructor injection" 是否有意义。
您知道您打算对给定的方法调用使用单个 BannerReplacer,因此您可以立即从如下所示的方法开始:
class Banner {
void doTheThing(arg, bannerReplacer) {
/* do the bannerReplacer thing */
}
}
请注意,此签名完全不依赖于 bannerReplacer 的 lifetime。更具体地说,BannerReplacer 的生命周期可能比 Banner 更长,也可能更短。我们只关心生命周期是否比 doTheThing 方法长。
class Banner {
void doTheThing(arg) {
this.doTheThing(arg, new BannerReplacer())
}
// ...
}
在这里,调用者根本不需要知道BannerReplacer;我们每次都会使用默认实现的新副本。调用者关心使用哪个实现可以自己传递。
class Banner {
bannerReplacer = new BannerReplacer()
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
和以前一样的想法;我们只是使用了一个生命周期更长的 BannerReplacer 实例。
class Banner {
Banner() {
this(new BannerReplacer())
}
Banner(bannerReplacer) {
this.bannerReplacer = bannerReplacer;
}
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
与以前相同的想法,但现在我们允许 "injection" 可以比给定的 Banner 实例更长寿的默认实现。
从长远来看,通过分析了解当前问题的需求,可以选择合适的工具。