我有一个名为StyleBundle的对象。
public class StyleBundle
{
public StylePricingType StylePricingType { get; private set;}
public decimal Price {get; private set;}
public IEnumerable<Style> Styles { get; set;}
public DateTime StartDate {get; private set;}
public TimeSpan Duration {get; private set;}
public bool IsTransient {get; set;}
public void ChangeStylePricingType(StylePricingType newStylePricingType)
{
this.StylePricingType = newStylePricingType;
}
}
此StyleBundle对象具有名为StylePricingType的属性。 StylePricingType是两种类型的枚举:
PerStyle
无限
StylePricingType将影响StyleBundle的整体价格。它会影响价格的方式是改变样式列表中保留的样式。 Unlimited StyleBundle将自动包含所有可用的样式,但PerStyle StyleBundle将允许用户手动选择他们想要包含的样式。
如果StyleBundle是瞬态的,我现在需要允许更改StylePricingType(先前的规则声明一旦StyleBundle被新建,你就不能改变StylePricingType。)
但是,为了进行此更改,我需要通过存储库/规范/服务对数据库进行检查...也就是说,但是我想这样做。
检查基本上在当前StyleBundle的相同持续时间内查找任何其他StyleBundle,并确保StyleBundle中的样式没有重叠。
由于在瞬态StyleBundle上更改此属性需要检查其他持久化的StyleBundles,实现此方法的最佳方法是什么?
使用构造函数注入:将服务注入到StyleBundle实体的构造函数中。我不喜欢这个,b / c我不喜欢将依赖注入我的实体,除非我需要这样做。此外,由于我不喜欢将依赖项注入构造函数的想法,因为它只需要更改StylePricingType的方法调用,我认为这是一个糟糕的设计。
使用方法注入:由于我只需要这个方法调用的服务,这似乎更有意义。但与此同时,我不喜欢用户能够在不知道他们正在运行数据库查询的情况下更改此类型的想法。另外,我仍然以不同的方式向我的实体注入服务,而且我真的不喜欢向我的实体注入任何东西。
使用域名服务:这似乎是最明确的。我可以创建一个StyleBundleService类,它具有ChangeStylePricingType方法,该方法使用存储库或规范来运行给定StyleBundle的检查。这样,代码中的要求非常明确,但缺点是代码仍然可以直接在StyleBundle对象上调用ChangeStylePricingType方法,而BYPASS则需要在服务上调用ChangeStylePricingType方法。即使我将StylePricingType设置为get; set;而不是私人集;并且在StyleBundle上摆脱了ChangeStylePricingType方法,代码仍然可以绕过域服务进行更改。
所以,这些似乎都是合法的方式来做这样的事情,那么使用DDD做到最好/最被接受的方式是什么?另外,也许我的StyleBundle对象试图做太多,应该分解成更小的类/功能,以便更加雄辩地处理这个需求变更?
麦克
答案 0 :(得分:0)
这是DDD中遇到的常见问题。 Udi Dahan在this post中讨论了类似的问题。选项1在this问题中讨论。选项2在SO的其他地方讨论(没有确切的链接),但和你一样,我不是粉丝,尽管它是最简单和最直接的方式。选项3通常与贫血领域模型相关联,但我经常发现它更为可取。原因是封装服务层是作为DDD的一部分而自然产生的东西 - 它将域层暴露给其他层,例如表示层或开放主机服务。此外,对域实体执行的动作可以表示为由服务处理的命令对象。在这种情况下,您可以:
class ChangeStylePricingTypeCommand {
public string StyleBundleId { get; set; }
public StylePricingType StylePricingType { get; set; }
}
class StyleBundleService {
IStyleBundleRepository db;
public void Process(ChangeStylePricingTypeCommand command) {
using (var tx = this.db.BeginTransaction()) {
var bundle = this.db.Get(command.StyleBundleId);
// verification goes here.
bundle.ChangeStylePricingType(command.StylePricingType);
this.db.Commit();
}
}
}
服务StyleBundleService
是访问存储库和其他服务的理想之地。
Udi概述的方法需要ChangeStylePricingType
引发一个域事件,该事件将订阅一个处理程序,该处理程序又执行所需的业务逻辑。这种方法更加分离,但更复杂,可能有点过分。基于域事件的方法的另一个问题是处理程序在事件发生后执行,因此无法阻止它,它只能处理后果。
答案 1 :(得分:0)
虽然我同意将这种行为外部化为StyleBundle是个好主意,但我通常会尽量避免使用服务。更确切地说,如果已知的模式名称更适合您真正希望对象执行的操作,我会尽量避免命名服务。
在您的示例中,我仍然不清楚您是否只想检查StyleBundle与您分配给它的新StylePricingType的有效性,如果捆绑不符合,则完全拒绝该操作, 或如果要根据新的StylePricingType调整包的内容。
在第一种情况下,规范似乎最适合(您在评论中提到,在将样式添加到捆绑包时,您已经使用过一个)。在第二个中,您需要一个实际上在
请注意,如果切换到PerStyle时要采取的操作与切换到无限制时的操作不同,则策略部分具有其所有含义,但再次根据您的解释,不清楚是否是这种情况。