在复杂的以域为中心的应用程序中,用于基本CRUD操作的DDD方法


9

我的公司正在从头开始重写我们的Web应用程序。它是大型企业级应用程序,在金融行业中具有复杂的领域。

我们使用ORM(实体框架)进行持久化。

本质上,我们的应用程序的一半集中在从用户那里收集原始数据,进行存储,然后包含大部分实际域逻辑的应用程序的另一半使用原始数据来创建我们的域图片,该域图片与原始数据有很大的不同原始输入,并将其传递到calc引擎,运行calcs,并吐出结果,然后将结果显示给用户。

在使用层的DDD方法中,CRUD操作似乎遍历域层。但至少在我们看来,这似乎没有道理。

例如,当用户转到编辑屏幕以更改投资帐户时,屏幕上的字段是存储在数据库中的确切字段,而不是以后用于计算的域表示形式。那么,当编辑屏幕需要数据库表示形式(原始输入)时,为什么要加载投资帐户的域表示形式呢?

在用户单击投资帐户屏幕上的“完成”,并对控制器执行POST之后,控制器现在几乎具有需要保存的投资帐户的确切数据库表示形式。但是出于某种原因,我应该加载域表示以进行修改,而不是仅将控制器模型直接映射到数据库模型(实体框架模型)?

因此,从本质上讲,我是将数据模型映射到域模型,以便可以将其映射回数据模型以持久化。这有什么意义?

Answers:


9

可以想象您实现了帐户创建页面,将表单发布直接映射到EF对象,然后将其保存到数据库中。

让我们进一步假设数据库具有各种限制,可以防止输入完全错误的数据。帐户总是有客户等。

一切似乎正常。但随后,企业制定了新规则。

  • 在星期四创建的帐户将获得2%的奖励利率。(假设利率是帐户字段之一)

现在,您必须将此逻辑放在某个位置,并且没有要放入的域对象。

DDD假定您将始终拥有此类规则,并且您可能会这样做。创建帐户必须进行各种检查,审核日志等,而不仅仅是“向数据库写一行”

假设没有持久性或MVC控制器,且没有额外的逻辑,请计划您的域。确保您捕获了所有需求,并且它们都在域模型中。


3
这是一个好方法。我讨厌找到与数据库详细信息混在一起的业务规则。+1
candied_orange

好点,但是,如果这些验证规则仅在创建和更新用户输入时适用,该怎么办?然后,一旦有了用户输入,运行计算时创建的模型便是完全不同的模型。我们应该为投资账户提供两种领域模型吗?一个用于用户原始输入的CRUD操作,另一个用于何时使用这些输入创建用于计算的域模型?
–wired_in

让我的问题感到困惑。您将不得不举一个完整的例子。如果您具有域逻辑,则应将其放入域对象。这并不意味着您不能在第一个域对象之后创建另一个域对象
Ewan

想象一个复杂的计算引擎。它需要进行计算的输入之一是投资帐户,但是对计算引擎而言,所有投资帐户都是一段时间内的收入流。投资帐户的域模型与用户为此投资帐户输入的原始输入完全不同。但是,当用户输入诸如名称,当前值等的基本输入时,仍然需要验证逻辑,但是它与计算引擎使用的模型无关。那么,这里有两个用于投资账户的领域模型吗?
–wired_in

.....或者在域中拥有投资帐户模型对于CRUD操作来说是过大的,应该只使用一些验证器属性或其他东西
–wired_in

7

这有什么意义?

简短的回答:不会

更长的答案:用于开发域模型的重量级模式不适用于解决方案中仅是数据库的那些部分。

乌迪·达汉(Udi Dahan)有一个有趣的发现,可能有助于澄清这一点

Dahan认为服务必须同时具有某种功能和某些数据。如果它没有数据,那么它只是一个函数。如果它所做的只是对数据执行CRUD操作,那么它就是数据库。

毕竟,域模型的目的是确保对数据的所有更新都保持当前业务不变。或者,换句话说,域模型负责确保充当记录系统的数据库是正确的。

当您使用CRUD系统时,通常不是数据记录系统。在现实世界中是记录的书,你的数据库仅仅是一个现实世界中的本地缓存表示。

例如,出现在用户个人资料中的大多数信息(例如电子邮件地址或政府颁发的标识号)都有不属于您的业务的真相来源-由他人的邮件管理员分配和撤消电子邮件地址,而不是您的应用。分配SSN的是政府,而不是您的应用。

因此,您通常不会对来自外界的数据进行任何域验证。您可能已经进行了检查以确保数据格式正确并进行了适当的清理 ; 但它不是您的数据-您的域模型没有否决权。

在使用层的DDD方法中,CRUD操作似乎遍历域层。但至少在我们看来,这似乎没有道理。

对于数据库是记录簿的情况这是正确

瓦尔扎(Ouarzy)这样说

但是,在处理大量遗留代码时,我发现了常见错误,无法识别域内部和外部。

仅当数据模型周围没有业务逻辑时,才可以将应用程序视为CRUD。即使在这种(罕见)情况下,您的数据模型也不是域模型。这只是意味着,由于不涉及业务逻辑,因此我们不需要任何抽象来管理它,因此我们没有域模型。

我们使用域模型来管理域内的数据。来自域外的数据已经在其他地方进行了管理-我们只是在缓存一个副本。

格雷格·扬(Greg Young)使用仓库系统作为解决方案的主要例证,其中记录册位于其他地方(即:仓库地面)。他描述的实现与您非常相似-一个逻辑数据库来捕获从仓库收到的消息,然后是一个单独的逻辑数据库来缓存对这些消息的分析得出的结论。

因此,也许我们在这里有两个有限的上下文?每个都有一个不同的模型investment account

也许。我不愿意将其标记为有限的上下文,因为尚不清楚还会带来什么其他行李。可能是您有两个上下文,也可能是一个上下文,您尚未熟悉的普遍语言中有细微的差异。

可能的试金石测试:您需要多少位领域专家才能涵盖此范围,或者仅一位以不同方式讨论组件。基本上,您可以通过反向处理康韦定律来猜测您拥有多少有界上下文。

如果您认为有界上下文与服务保持一致,则可能会更容易:您是否应该能够独立部署这两项功能?是的,表明了两个有限的上下文;但是如果需要保持同步,那么也许只是其中之一。


有验证和默认逻辑,但是它仅在创建/更新投资帐户的原始输入时适用。然后,当我们将其用作计算引擎的输入时,将为投资帐户使用更为丰富​​的模型。因此,也许我们在这里有两个有限的上下文?每个帐户都有不同的投资帐户模型”
–wire_in

几年后,我才回到这个话题,由于某种原因,您的评论现在比以前引起了更多共鸣。这里有很多好东西,但是您能为我澄清一件事吗?您说过:“毕竟,域模型的目的是确保对数据的所有更新都保持当前业务不变。” 这将适用于我们应用程序中保存/更新信息的部分。另一部分只是一个计算引擎。它以数据的表示形式作为输入并吐出结果。那不是领域模型的一部分吗?既然不影响存储的数据?
wired_in

2

在您的域中,您不必知道数据库甚至存在。

您的域与业务规则有关。当建立您的数据库的公司倒闭时,需要生存的东西。也就是说,如果您希望您的公司生存下来。当这些规则不在乎您已更改持久数据的方式时,这真是太好了。

数据库详细信息存在并且需要处理。他们应该住在其他地方。将它们越过边界。仔细控制您如何跨越边界沟通,这不是边界。

Bob叔叔有话要说要把数据放在什么地方:

通常,跨越边界的数据是简单的数据结构。如果愿意,可以使用基本结构或简单的数据传输对象。或者,数据可以只是函数调用中的参数。或者,您可以将其打包到一个哈希图中,或将其构造到一个对象中。

重要的是,隔离的,简单的数据结构跨边界传递。我们不想欺骗并传递实体或数据库行。我们不希望数据结构具有任何违反“依赖关系规则”的依赖关系。

[…]当我们跨边界传递数据时,数据总是以最方便内圆的形式出现。

清洁建筑

他还解释了外层应该如何成为内层的插件,这样内层甚至都不知道外层的存在。

清洁建筑备忘单

遵循类似的方法,您将有一个忽略数据库的好地方,您可以在其中担心输入验证规则,必须以某种方式保留输入的规则,运行计算的规则,将这些结果发送到任何输出的规则。阅读这种代码实际上更容易。

就是这样,或者您决定您的域实际上只是在操纵数据库。在这种情况下,您的域语言是SQL。如果很好,但不要期望您的业务规则实施能够在持久性变化中幸免。您最终将需要完全重写它们。


我们使用的是ORM(实体框架),因此我们的数据库已经被抽象出来,但是数据库模型中的数据模型(实体框架类)自然是一对一的。问题在于,在我们应用程序的某些部分中,用户实际上只是在更新数据模型(屏幕只是文本框的列表,其中每个文本框都是数据库中的一个字段(数据模型)。​​)
wire_in

因此,我看不出在进行CRUD操作时不只使用原始数据(数据模型)的表示形式的原因。我们有一个用于计算的复杂域表示形式,这就是我认为的域模型,但是我不明白为什么要在应用程序的CRUD部分中加载该图片。
–wired_in

通过“使用原始数据的表示形式”定义您的意思。输入数据,根据领域规则验证数据,以某种方式持久化数据,针对数据进行计算,将结果输出到任何内容。我想念什么吗?
candied_orange

我想说的是,我们从用户获得的投资帐户原始数据并不是我们如何在应用程序的主要部分中表示该投资帐户,例如将其用于计算时。例如,我们可能将一个布尔输入保存在名为IsManagedAccount的数据库中。用户通过编辑屏幕上的单选按钮向我们提供此信息。因此从数据库到屏幕的表示是1到1。当稍后在应用程序中建立域模型时,我们可能具有ManagedAccount类,因此没有布尔属性。两种结构截然不同。
–wired_in

因此,当用户仅在编辑屏幕上编辑原始输入时,为什么我要加载域图片,然后增加很多复杂性,以某种方式将强类型的ManagedAccount类映射回平面表示形式,该表示形式只是具有IsManagedAccount的单个类属性?
有线上网'17年

1

应用DDD理论:

该域中有两个绑定上下文:

  • 投资账户的计算。投资账户数学模型是一个要素,也许是合计。
  • 核心财务。客户投资账户是实体之一。

每个有界上下文可以具有不同的体系结构设计。

例:

客户投资账户是一个实体(可能是一个聚合实体,取决于域),数据的持久性是通过实体的存储库(RDB或其他类型的DB,如OO数据库)实现的。

CRDD操作没有DDD方法。将数据库字段绑定到对象的数据违反了设计原则。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.