在Vaughn Vernon的领域驱动设计蒸馏书we can read中,我们应该尽量避免创建可能过于抽象的技术抽象,并试图通过坚持普遍存在的语言的概念来更加明确。
在我工作的地方,我们已经构建了几个跟踪应用程序,并且几乎每个应用程序都存在同一事物的多个特化问题,最常见的是常见行为,但数据和验证规则不同。
例如,想象一下事件记录应用程序,通过电话报告各种事件(例如车祸,火灾,抢劫)。信息收集过程与每个事件类似,但捕获的数据可能会有很大差异,以及限制此数据的验证规则。
到目前为止,我们总是通过非常技术性的抽象解决这些问题(这是一个过于简化的模型,但你应该明白这一点):
如您所见,DataValidationRules
,DataFields
和DataEntries
抽象与事件日志记录业务关系不大。实际上,它们是在任何域中表示具有不同数据的多个实体专业化问题的非常通用的解决方案的一部分。
我想摆脱这种非常抽象的模型,但与此同时,我并没有看到将业务概念明确化的正确方法。我理解答案在每个领域都会有所不同,但从本质上讲,我是否应该考虑每个专业领域有一个单独的课程?例如。 CarAccidentIndicent
,FireIncident
和RobberyIncident
?
由于专业化数量非常有限,它似乎可以管理,但如果我有数百个呢?
用户界面怎么样?这意味着我不得不放弃生成UI的通用方式。
在考虑了一些之后,我想我可能已经找到了一种更好,更简单的方式来表达我对DDD,OO和建模许多专业化的担忧。
一方面,我想提出一个明确忠实于泛在语言(UL)和模型领域概念的模型。另一方面,我正在努力尊重“赞成过继承”这句口头禅我已经习惯了。
似乎两者都是相互矛盾的,因为为了启用合成,我必须引入很可能不属于UL的抽象(例如Entity--Field
组合),当涉及到显式建模时,我不会除了每个专业化一个类的继承之外,还可以看到任何其他方式。
我是否错误地试图避免继承来代表数百个主要在数据结构方面而不是行为方面存在差异的专业实体?
然后,假设他们在行为方面确实存在很大差异,我也会遇到同样的困境。
为了更明确地设计选择:
在一种情况下,组合可以动态实现,而不需要每个专门的组合多个类:
class Incident {
Set<Detail> details;
IncidentType type;
}
interface Detail {
public DetailType type();
}
class SomeDetail implements Detail {
...
}
class SomeOtherDetail implements Detail {
...
}
在另一种情况下,合成是静态的,每个专门的合成需要一个类:
class CarAccidentIncident extends Incident {
SomeDetail someDetail;
SomeOtherDetail someOtherDetail;
}
class SomeDetail {}
class SomeOtherDetail {}
显然,第二种方法更明确,为特定的行为和规则提供了自然的家园。在第一种方法中,我们必须引入一些抽象和技术概念,如Operation
和DetailValidation
,这些概念可能与UL不一致。
虽然有少量不同的专业,但我可能会选择后者而不是第二种,但因为它们中有很多似乎我更倾向于动态构图(甚至认为动态不是必需的)。我应该吗?
答案 0 :(得分:2)
问题是,DDD不一定适合所有系统。它特别适合具有复杂业务规则的大型系统。
如果需要表达以捕获FireIncident本质的业务规则很简单,可以在DataValidationRules
记录和一组DataFields
中进行编码,那么这表明这些规则可能不会需要DDD实现的复杂性。
但是,如果您承认这一点,您可以将您的观点转向打算实际构建纯数据验证引擎。数据验证域应该包括诸如数据验证规则和数据字段之类的实体,并且会考虑与规则和字段的生命周期相关的这些问题 - 例如&#39;如果验证规则发生变化会发生什么 - 所有先前已经过验证的现有记录是否需要重新验证?&#39;
如果数据验证规则本身的生命周期足够复杂以保证它,那么无论如何都要使用DDD来实现该域,尽管如果您发现没有复杂的规则或流程,您仍然可以选择使用CRUD数据验证领域。
更进一步的结果是,您的域名专家不再是您的最终用户(了解车祸和火灾事故的人),他们现在是制定验证规则和字段的人(最有可能是专家)。如果使用DDD,您需要询问他们需要哪些类型的规则以及他们需要规则如何工作,以及使用他们用来讨论艺术和制作过程的无所不在的语言来实现验证规则。
反过来,那些人将会编程&#39;下一级系统(您可能会说他们正在使用针对事件日志记录域定制的4GL language)使用您的数据验证引擎。问题是,他们的领域专家将成为了解车祸的人。但专家们并不会严格使用DDD来制定车祸规则,因为他们不会用软件来表达他们的模型,而是使用数据捕获和验证引擎的约束语言。
自从您的更新以来,一直在思考这个问题,并且还有一些想法/问题:
您最关心的是围绕创建/更新表示数据验证规则。有助于理解的是 - 除了数据验证之外,您的实体代表什么行为/规则?即,在事件管理系统中,您可以通过一组状态跟踪事件,例如Reported,WaitingForDispatch,ResponseEnRoute,ResponseOnSite,Resolved,Debriefed?在保险系统中,您可以跟踪报告,已验证,等待退款,已关闭等
我问的原因是,在没有这种生命周期行为的情况下 - 如果你的系统的主要目的是纯数据验证,那么我回到原来的想法,想知道DDD是否真的是这个系统的正确方法,因为DDD在存在复杂的建模行为时会带来最大的价值。
如果你确实有这样的生命周期或其他复杂的行为 - 那么一种可能性是从不同的有界语境的角度考虑这种方法 - 即有一个有限的数据验证上下文 - 它使用你所描述的方法更多技术抽象 - 因为它是表示验证的有效方式 - 但是从生命周期管理的角度来看另一个上下文,您可以更多地关注业务抽象 - 如果所有事件都遵循相似的生命周期集,那么该上下文将具有特定实体的数量要少得多。
保持实体在上下文之间同步是一个完整的主题,但如果您采用服务总线或事件类型技术并在事情发生变化时发布事件,则不会太麻烦。
您的业务专家如何表达更改验证规则的请求?你是如何实现它们的?我猜你所说的话,他们可能会用“FireIncident”这样的域名来表达它们。但实现很有趣 - 您是否必须在SQL中制作数据修改语句,并将其作为部署的一部分应用?
似乎两者都是冲突的,因为为了实现组合,我必须引入很可能不属于UL的抽象(例如实体 - 字段组合)
我不认为这是真的 - 组合不需要引入技术抽象。无论是组合还是继承,目标都是将洞察力提炼到域中以发现常见模式。
e.g。寻找常见的行为或数据验证集,并找到描述这种共性的商业语言术语。例如您可能会发现RobberyIncident
和FireIncident
都适用于Buildings
。
如果使用继承,您可以创建BuildingIncident
和RobberyIncident
,FireIncident
会扩展BuildingIncident
。
如果使用合成,您可以创建一个值对象来表示Building
,RobberyIncident
和FireIncident
都包含一个Building属性。但是RobberyIncident
还包含Robbery
属性,FireIncident
也包含Fire
属性。 CarAccidentIncident
和CarRobberyIncident
都包含Car
属性,但CarRobberyIncident
还包含与Robbery
属性相同类型的Robbery
属性在RobberyIncident
- 假设它们是真正的共同行为。
您可能仍然有数百个表示专门事件类型的类,但它们只是由一组值对象属性组成,这些属性表示它们组成的一组常见模式 - 这些值对象可以而且应该是无处不在的语言概念。
答案 1 :(得分:1)
我对此的看法是,并非所有信息都与域名相关。
我认为在很多情况下,我们会尝试在“全有或全无”的情况下应用技巧。我们可能需要关注工作的正确工具&#34;。在克里斯提供的答案中,他问了问题&#34;何时使用DDD?&#34;提及&#34;问题是,DDD不一定适合所有系统。&#34;我认为DDD可能不适合系统的某些部分。
DDD是否可用于创建文字处理应用程序?我真的不这么认为。虽然一些好的适当的OO会有很长的路要走。
DDD绝对适用于系统的业务行为。但是,可以使用更加技术/通用的方式对位进行建模,将提供转换为更有趣的业务功能。我确信这些事件最终会在某些业务流程中出现。一个例子可能是Claim
。该商家非常有兴趣跟踪索赔和索赔金额,但该索赔的来源并不是很有趣。对于所有意图和目的,&#34;启动文档&#34;可以使用笔和纸填写并扫描以与所述权利要求相关联。甚至可以使用纯文本输入在系统上开始新的声明。
我参与了许多系统,其中许多外围数据被吸收到系统中,但实际上它并没有真正贡献很多(收益递减等法则)。
我曾经在贷款系统上工作过。原来已有20年历史的系统是用C#重写的。主要的移动位:
总而言之,它真的是一个简单的系统。好吧,800多个表后面和堆栈的开发人员/ BA和系统有点像怪物。人们甚至可以将股票和业权契约作为保证。现在,我的意思是扫描一些这些信息并将其链接到贷款。然而,不知何故,一些商界人士认为他们绝对必须拥有&#34;这个信息在系统中。不过,我认为它不是核心。
另一方面,我研究的另一个系统计算了保费。它的模仿非常像商业,并且是一个维护噩梦。然后通过简单地定义适用于给定输入的计算来重新编写它。有一些值的查找表等等,但没有业务处理。
有时我们可能需要将移动位抽象为有意义的输入或输出,然后在我们的域中使用它。我认为UL应该由我们自己和领域专家使用,但它并不意味着我们不会最终使用不 UL的技术概念,我认为 没关系。我确信领域专家对SqlDbConnection
并不在意,即使我们要在我们的代码中使用其中一个:) - 同样我们可以在适当的域名。
响应您的更新和问题:我不会创建具体的类,除非它在UL中真正 功能。另外,我仍然赞成合成而不是继承。我通常在必要时实现接口,并在继承时使用抽象类,只是在它有用时放置一些默认行为。
与任何设计一样,UL代表具有细微差别的模型。我们可以在不使用域事件的情况下应用DDD。当我们做使用域名事件时,我们甚至可以选择事件来源。事件采购与UL几乎没有什么关系,就像术语&#34;聚合&#34;,&#34;实体&#34;或&#34;价值对象&#34;将。 UL将专门针对域/域专家,当我们作为域建模者彼此交谈时,我们可以根据DDD战术模式描述各种模型,以便引入一些特定的UL概念。
我们必须听取域专家如何描述问题空间。一听到&#34;当&#34;,如其他许多地方所述,我们知道我们可能正在处理一个事件。我们可以用同样的方式听取域专家如何讨论聚合。例如(完全虚假的例子):
与您的示例更加松散相关:
在这两者之间,我们可以看到他们如何引用与问题空间的交互方面的明显差异。当一个领域专家用非常广泛的术语来思考某些事情时,我认为我们做同样的事情是谨慎的。
另一方面,如果谈话更符合这一点:
现在我们有更具体的东西。这些必然是相互排斥的,因为如果他们只讨论具体细节,那么我们会选择特定的&#34;。如果他们首先提到广泛的术语和然后具体细节,我们也可以广泛地工作。
这是我们的建模很难做到的。它与我们在Address
中作为聚合与价值对象&#34;辩论&#34;具有相同的细微差别。这一切都取决于具体情况。
这些事情将变得棘手并依赖于域名以便正确行事。正如Eric Evans所提到的那样:可能需要几个模型来获得恰到好处的东西。这必须基于对域的一种体验。