【问题标题】:Domain driven design - incremental save from UI领域驱动设计 - 从 UI 增量保存
【发布时间】:2019-04-15 04:43:25
【问题描述】:

我是 DDD 的新手,正在阅读有关它的文献,但在应用某些概念时遇到了麻烦。 我正在展示我正在构建的应用程序的简化视图。这是一个房屋贷款申请系统。 UI 有类似向导的步骤来收集信息,比如第 1 步是收集申请人信息,第 2 步是收集财产信息,第 3 步是捕获批准或拒绝的决定。在第 1 步中,每个应用程序都被分配了一个唯一的 ID。我的挑战是如何对每个步骤的增量保存进行建模。 贷款申请是我的总根。根据我的阅读,每个根只有一个存储库,并且必须将整个根保存在一起以使其有效。但是 UI 会增量收集信息,并且在每一步中,应用程序实体都是有效的 - 当我保存第 1 步中的数据时,我的贷款申请对象是有效的。保存步骤 2 中的数据后,贷款申请对象仍然有效。 在此处寻找有关如何设计 Api 和存储库的建议?如果 agg root 在每一步都有效,并且可以小步保存,那么暴露一个保存 api 有什么意义呢?是否应该有 3 个单独的 Api 暴露给 UI,并且这 3 个 api 调用 3 个单独的 repo 类或 1 个 api 在一个 repo 上调用 3 个单独的方法?我正在使用实体框架保存到数据库。 谢谢。

【问题讨论】:

    标签: domain-driven-design


    【解决方案1】:

    应用程序的要求以及您对其建模的方式将影响您的操作方式。

    (注意:我将使用 AR 作为聚合根)

    如果您有 LoanApplication AR,它需要包含有关 申请人Property 的信息。

    假设每个Applicant 都会有一个独特的帐户,这样您就可以跟踪Applicant 有多少贷款。

    在这种情况下,Applicant 将是一个实体,并且可能是一个 AR。它将拥有自己的存储库:ApplicantRepository。在这种情况下,Applicant AR 需要从您的 LoanApplication AR 中引用。

    这意味着在向导的第一步中,您可以使用 ApplicantRepository 搜索 Applicant 或创建一个新的。如果要创建一个新的,您可以在第一步中创建并保存它。稍后您可以从 LoanApplication

    引用 Applicant(通过引用或 ID)

    如果您不想这样,那么 Applicant 可以是一个值对象,它的信息将存储在 LoadApplication AR 中。

    同样的事情也适用于 Property:您可以拥有一个带有 PropertyRepositoryProperty 实体,或者只存储 PropertyInfo 值对象LoadApplication AR

    另一个重要的事情是你的LoanApplication 有一个生命周期。根据它的当前状态,它的不变量可能会改变。拥有一个会经历不同阶段的 AR 是可以的。以在线商店为例。当您从亚马逊订购商品时,您的订单可能处于已批准或待处理状态(或处于其他状态),这是其生命周期 的一部分。当您想完成订单时,系统可能会在提交之前要求您提供付款详细信息。

    在您的 ca 中,您可以通过以下状态为 LoadApplication 创建一个生命周期PendingSubmittedForApproval, Approved, Rejected 等等。它可能还需要关于它为什么被Rejected等的额外信息。

    如果您想保存有关创建 LoadApplication 的过程的信息,您可以分配一个状态,表示您 LoadApplication 仍在创建过程中:待处理或进行中。这样,如果您的应用程序在中间崩溃,它可以通过获取 LoadApplication 并检查其状态来恢复。

    您可以在 LoadApplication AR 中添加与其状态相关的行为(例如,如果它不在 SubmittedForApproval 中,则不允许转换到 Approved 状态> 状态并且不允许更改 InProgress 状态的 Property(您不想更改 Property当状态为 Approved 或 SubmittedForApproval 时的申请人

    实现保存:

    如果您决定 LoadApplication Aggregate 将包含三个实体:LoadApplicationApplicantProperty,然后将所有这些实体一起保存和加载,因为聚合是一个事务边界。该指南可以帮助实现 Save,但它可能很棘手。

    这取决于几个因素:

    • 你是什么数据库(SQL、MongoDB)?

    • 您是使用框架(NHibernate、Mongoose)还是使用本机 API(使用原始 SQL)?

    由于只有 LoadApplicationRepository 会使用 ApplicantProperty 保存 LoadApplication,因此即使Applicant 将再次保存到数据库,因为不会对其进行任何更改。您只会用相同的数据覆盖现有数据,性能不是很酷,但这不是您的逻辑问题。

    另一方面,如果您使用 ORM,它们可以检测对象中的更改并仅生成所需的查询以仅更新对数据库的新更改。在这种情况下,如果您说将 Property 添加到 LoadApplication,则将选择它并仅更新数据库中的 Property

    例如,假设您正在使用带有 (N)Hibernate 或 EntityFramewok 的 SQL 数据库,您的 ORM 将跟踪添加属性的更改并生成 SQL 以将其插入到数据库中的 Properties 表中,但是不会为已经存在的Applicant生成更新插入,因为它没有改变。

    如果您正在使用例如原始 SQL 编写自己的逻辑,那么您将必须自己编写跟踪更改的逻辑。

    一种方法是在 LoanApplicaton AR 中添加更改集合,其中将包含更改/事件(ApplicantAssigned、PropertyAssigned、SomethingChanged 等),以便您可以在 Save 方法中使用它们来根据更改生成 SQL。当您保存聚合时,您可以清除更改。

    这是一篇关于建模聚合的精彩文章: http://dddcommunity.org/library/vernon_2011/

    以下是关于领域事件的一对: https://www.martinfowler.com/eaaDev/DomainEvent.html https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/

    【讨论】:

    • 感谢您的详细解释。我可以要求澄清 - 如果只有 1 个 AR 存储库,repo 如何处理子实体的保存。有一个生命周期 - 有时 AR 只会填充客户,有时会同时填充客户和财产,有时所有 3 - 客户、财产和决策都将可用。 ARRepository 的一种“保存”方法如何处理所有子实体的保存?如果 AR 已经保存了客户并且正在保存属性对象,则通过在存储库上调用单个“保存”,客户将作为保存属性的一部分再次保存。
    • 欢迎您 :) 我将添加澄清作为更新我的答案,因为 cmets 的字符数有限,我想给出详细的解释
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-29
    • 1970-01-01
    相关资源
    最近更新 更多