人寿保险销售平台的领域驱动设计和事件风暴案例分享 -James Hickey


几年前,我领导了一个在线销售人寿保险新平台的网络开发。我们将介绍以下几点:

  • 事件风暴:这是什么以及如何开始对业务域进行建模
  • 从领域事件的角度思考系统或业务域如何真正帮助澄清问题
  • 人寿保险业务可能面临的一些重要问题
  • 如何更好地处理与外部系统/ API的交互
  • 某些分布式模式如何改善系统的用户体验

什么是事件风暴?
事件风暴是通常用来发现和了解业务运作方式的研讨会。您邀请所有利益相关者,并用便签纸表明领域事件中了发生什么,他们可能会导致什么样的影响或其他流程。
我还发现事件冲动甚至在较小规模和团队讨论或设计讨论中也很有用。

过去的设计概述
该系统旨在让(a)主要保险提供商和(b)第三方保险提供商允许其客户在线申请人寿保险。它是可配置的,因为第三方提供商可以选择将包括通用申请流程的哪些部分,入口点在哪里以及其他自定义项。
当第三方提供商处理应用程序时,流程如下所示:

  1. 从第三方提供商的客户填写的初始表单中接受一些HTTP POST数据。
  2. 用户继续填写在线人寿保险应用程序(分多个步骤)。
    1. 首先,一般联系信息。
    2. 接下来,有关受益人的信息等
    3. 然后,一份医疗问卷。
    4. 等等。
  3. 完成后,用户将提供他们的银行信息。
  4. 然后,通过调用远程API处理该应用程序(同步-在用户等待UI刷新的同时)。
  5. 最后,显示该应用程序是否被接受(用户需要执行下一步)。

一切都在一个庞大的代码库中完成。所有的Web UI和业务逻辑都在一起。
这种方法的一些问题是:
  • 缺乏代码质量
  • 可维护性方面的困难
  • 缺乏业务流程的代码结构记录
  • 长时间运行的同步HTTP POST可能会超时并导致处理错误
  • 和更多...

探索我们的领域
让我们开始根据域中发生的事件来查看业务流,以概述我们正在处理的内容。
请记住,这不是对所有域事件的详尽了解,而是非常简化的外观:
ApplicationStarted、PersonalInformationProviede、MedicalQuestionsProvided、ApplicationCompleted

 之所以将PersonalInformationProvided视为域事件,是因为发生了其他响应。
例如,保险公司坚持要求系统发送带有令牌的电子邮件,以便建议的被保险人(即申请人)可以在以后的日期恢复其申请。
MedicalQuestionsProvided可能会导致申请人丧失资格,所选保险产品失去资格,在整个剩余表格中启用完全不同的路径,等等。

进入下一步:

如您所见,这是使事情变得有趣的地方。

在BankingInfoProvided之后,系统将远程调用保险公司的“特殊” API,该API将查看过去的申请人历史记录,银行历史记录,病历等,并做出决定。该决定是通过相同的HTTP POST返回。
在某些情况下,申请的被保险人必须在以后的日期进行身体检查。在此之前,他们的申请一直处于待处理状态。
如果申请通过(ApplicationAccepted),则将创建保险单(PolicyCreated)。否则失败,这意味着没有任何Policy被创建。
首次支付保单(FirstPolicyPaymentRecieved)后,该保单将“激活”。如果在前30天(或任意持续时间)内未激活Policy,则该策略将立即被取消。

有界上下文
在现有系统的设计中,没有边界上下文的概念。一切都存储在一个庞大的XML文件中(是​​的……我们在过时的行业中必须处理的事情)。我们知道我们需要对此进行拆分。应该怎么办?

重要事件
“关键事件”是最重要的事件,因为它们是领域内重大变化的驱动力。
在这种情况下,最重要的事件是PolicyCreated。这是申请正式转变为真实保险单的时候。这就是用户首先想要的最终目标。
有趣的是,这也是面向用户的Web应用程序结束之处,并是后端办公系统的保险Policy开始之处。

子域
另一个有趣的领域(除非您熟悉该行业,否则您可能不会意识到)医疗问卷非常复杂。根据您回答某些问题的方式,可能会发生许多不同的事情。
围绕此特定领域的代码和业务逻辑是一个热点。
由于该领域包含的复杂性,我有兴趣将这个领域作为一个子域进行研究,并将其视为有界的上下文。

子域与有界上下文不同。子域仍然是与业务相关的部分,而有限的上下文是关于我们的软件所具有的边界。但是,很多时候它们确实匹配-尤其是在建模开始时,就像我们在做的那样。但是,如果业务结构发生变化,则子域可能会发生变化(随业务变化),而绑定的上下文仍会烘焙到软件中。

从业务的角度来看,这里MedicalQuestionsProvided绝对是关键事件。它是申请被保险人是否有资格获得保险或有资格获得其他“额外”保险附加产品的主要驱动力。(在现有系统中,系统的这一部分最难构建和维护!)
这是有限上下文的主要概念的亮点-您可以在特定区域或复杂的问题空间周围划分边界,并使该复杂性保持隔离状态。没有人需要知道它是如何工作的。它只需要知道调查表的末尾会发生什么。
请记住,尽管我们不能随心所欲地创建有界上下文。在这种情况下,我们确定了(目前看来)一个关键事件。

语言
同样,如果我们与领域专家深入研究,我们会发现在医学问题中使用的(普遍存在的)语言是非常特定于医学问卷的。
例如,在这种情况下,当谈到申请人时,是指他们的健康状况,身体健康状况等。
这是在整个域中唯一使用此语言的有关申请人的地方。

子域之间的过渡
您会注意到,数据流是从一个上下文流到另一个上下文,然后又返回到同一上下文。而通常,在典型的DDD示例中,您会看到数据从一个上下文流到另一个上下文,并且该流再也不会返回原始上下文。
我的直觉是,这些复杂的子域在某些总体业务流程或其他父域中间散布,在现实世界域中很常见。

朴素API Calls和 Saga比较
现有系统的主要问题之一是,它将直接从Web应用程序发布HTTP POST到保险提供商的“特殊” API,该API会批准或拒绝该应用程序。
同样,这很容易导致出现以下问题:

  • 由于网络问题导致HTTP超时
  • 端点关闭时的错误
  • 该请求花费的时间太长且超时...

由于这是一个白标产品,我们无法控制该API的性能和可用性。
为了更清晰地概括,这里是所需的步骤(准系统):
  • 提交应用程序(通过HTTP POST)
  • 等待...
  • 确保重试错误...
  • 如果外部系统从不响应,则中止
  • 如果确实响应,则将结果提供给用户

这种长时间运行的作业/流程通常最好使用saga模式来完成

处理这些类型的长时间运行的作业时,其他考虑因素可能是路由单模式流程管理器模式 -两者均与saga模式相似。

使用saga模式,我大致会想到以下内容:

这看起来要复杂得多,但是请耐心等待一下。
请注意,BankingInfoProvided事件将不会启动HTTP POST这种“简单明了”的提交信息方式,而是将启动一个长期运行的后台作业。具体来说,启动的是ApplicationSubmissionSaga。

这个Saga有两个处理程序:
[list=1]

  • 第一个将尝试调用API并提交应用程序。
  • 第二个将接收异步事件,通知其提交状态,并发出其他事件或逻辑。
    为什么?这给了我们什么?
    弹性系统
    在原始设计中(直接HTTP POST)-如果外部API 甚至没有运行怎么办?
    哦...
    我猜该用户不走运。他们甚至无法提交他们的申请...
    如果使用saga模式会发生这种情况怎么办?
    Saga流程将失败,然后进入睡眠状态并稍后重试(因为它是后台进程)。如果外部系统第二天恢复正常,那么Saga将成功提交申请并继续!
    注意:此重试过程在上图中由上的橙色齿轮指示SubmitApplicationForApprovalStep。
    这种处理分布式事务或长时间运行的业务流程的方法(即使您不拥有外部API)也可以帮助您构建可能会失败并且预期会失败的系统。

    对UX的影响
    让我们根据对UX的影响来比较这两种设计。是的,建模上的差异极大地影响了用户体验。
    采用原始的原始设计,如果外部系统出现故障,则用户将无法完成其应用程序。因此...用户将不得不稍后返回该网站并“重试!”
    使用Saga这种模式的地方就在这里:用户将“完成”他/她的应用程序,我们将告诉他们:“一旦我们处理完您的应用程序,您将在接下来的24小时内收到一封电子邮件。”
    用户回家,并最终在不久的将来的某个时间收到一封电子邮件,告诉他们他们需要了解的所有信息。
    但是,如果外部系统现在关闭了怎么办?
    用户不知道。他们走了。我们对系统进行了设计,使我们不需要用户与外部API进行通信并处理故障。
    用户的体验是什么?
    太棒了
    这意味着巨大的意义。这是易于使用与那些烦人且难以使用的系统之间的差异。
    这会影响保险公司的品牌和声誉。

    正确建模域的重要性
    这回到了很好地建模域的想法。这都是建模。我们还没有编写任何代码,但是从概念上讲,我们可以知道两种设计都会发生什么。
    这就是为什么对于想要成功并为用户提供最佳体验的公司聘请擅长对此类业务领域和流程进行建模的开发人员和工程师的原因。
    从字面上看,这可能是公司成功与失败之间的区别!
    这是为什么要学习作为开发人员/工程师使用这些工具建模的重要性的一种观点。

    问题!商业利益相关者不同意
    利益相关者希望保持网站能够将结果立即显示给客户,他们不喜欢这一事实:用户现在网站上无法及时获得申请状态的反馈。
    我们的新设计意味着应用程序批准/拒绝的结果现在是一个后台过程-与我们的Web应用程序断开了连接。以下是你可能已经想到了一个解决方案:在Web应用程序也可以订阅ApplicationAccepted,ApplicationDenied和ApplicationPending事件,并使用网络套接字(使用SignalR等),以“推”的结果返回给用户的浏览器。
    这甚至可能包括显示浏览器通知(即使我们都讨厌它们)?
    无论哪种方式,这都是满足我们到目前为止所有要求的一种方式。

    结论
    在这个领域上,以上看起来有些原始。还有更多的东西。
    希望您确实学到了一些有关建模业务流程的知识。有时候,仅通过更改我们在业务流程中处理步骤的方式,我们就能产生巨大的影响!