阅读 151

DDD 实践手册(1.Get Started)

近几年随着微服务的流行,领域驱动设计(Domain-Driven Design) 重新回到了主流视野中。我自己最早是在大约 2003 ~ 2004 年左右了解到 DDD 的概念,之后一些金融行业的业务系统中尝试运用了 DDD 的理念进行系统设计,期间的确感受到了 DDD 与其他架构设计不同之处,但也遇到了不少问题。之后的几年中,我的工作内容逐渐转移到了互联网行业以及与数据相关的工作,在项目中也不太有机会使用 DDD。不过凑巧的是最近的一个项目中,我重新回到了我比较熟悉的金融核心系统研发的领域,而客户的需求之一就是使用 DDD 对核心遗留系统进行重构与拆分。在经过了 10 年之后,无论是技术还是业务都发生了不少的变化,而我自己对系统架构与设计也有了新的认识,重拾 DDD 这把扳手对我而言发生了有趣的化学变化,促使我对以前的一些概念与解决方案做了细致的梳理,也因此有了这个系列的文章。

我希望通过这个系列的文章不仅回顾 DDD 核心的知识与概念,还能够分享我在实际项目中遇到的问题与对应的解决方案。我比较认同的一个观点是架构师最大的工作是做出取舍,也就是在技术,业务价值之间找到 trade-off。所以我在项目中给出的方案不一定是最优的,也不一定适合你的项目,但是我会把「为什么」这样做告诉你,接着就是你怎么在自己的项目中进行取舍了。

Why DDD

在开始详细分析 DDD 之前,我们先看一下 DDD 尝试解决什么样的问题。DDD 的相关文章与书籍中,被提及最频繁的关键字一定非「复杂性」莫属。对于复杂性的概念,我们先不展开讨论,把它的定义限定在所谓的「业务系统」的范围,那么「业务系统」又是个什么系统呢?说白了你可以认为就是个基于关系型数据库的 CRUD 系统 😁

既然「业务系统」无非是对关系型数据库做 CRUD,那么复杂性体现在哪呢?我的答案是体现在繁琐的业务规则,以及未来不可知的业务变化。这么说你肯定觉得等于没说,所以我来举个栗子。很大概率你有过买保险的经历,不一定是你买,很可能是你父母为你购买保险,那么你购买保险在保险核心业务系统中是怎么实现的呢?

你想购买保险的这个行为称之为「投保」,而保险公司接受你「投保」请求,并出具保险单的行为称之为「承保」,简略描述从投保到承保的过程大致需要经历以下这些步骤。首先系统需要判断你投保的是个人保险,还是团体保险,这两个走的是两个截然不同的流程,这里我们只说个人保险的流程。对于个人保险,系统先要进行「核保」的流程,你可以认为是审核你有没有资格购买某项保险。而核保的规则一般分为基础规则与产品相关的规则。基础规则一般检查你的身份,国籍,年龄等信息,例如一定是本国公民,且已经成年等。而产品相关规则则是按照你需要购买的保险产品而定义的专有规则。

现在又提到了「保险产品」这个概念,保险产品按照不同的维度有不同的区分方式,例如按照保障期限划分,有极短期险,短期险,长期险和所谓的终身险。按照保障范围(国内一般称之为「责任」),会分为医疗,意外,重疾,寿险等。还有些特殊的产品,例如万能,投连,年金等。这些产品的核保规则都是不同的,可能对于你现在的职业,年收入,健康状态都有不同的要求。

在通过核保之后,就需要按照你的个人状况计算费率,也就是你应该缴纳的保险费用。计算费率的过程也很有意思,一般分为表定法与公式法。顾名思义,公式法最好理解,就是由保险公司的精算部门定义一个费率公式,把变量带入之后计算即可。而表定法是通过一张称为「费率表」的东西来计算费率,具体计算逻辑我就不谈了,你只需要知道每种保险产品都有自己对应的费率表或是公式。

计算费率的过程中需要来自各方面的信息,例如被保人的年龄,职业类型,缴纳保费的方式(一次性缴清,或是分期缴纳,称之为趸缴与期缴)等等。计算出保费之后还需要计算代理人(就是介绍你买保单的那个人)佣金,这也是个非常冗长的流程。之后还有大段的流程,例如生成财务的收费记录,各种单证,打印记录等。

写到这我就不继续了,再写下去就变成保险系统的科普文了,但是你可以发现上述的流程有两个特点:

  1. 涉及的节点非常多,且计算的逻辑牵涉到很多变量,且有很多的分支判断;

  2. 牵涉到很多专业术语;

事实上我已经省略了很多业务规则,如果你被要求实现这样一个核心业务系统(承保只是保险核心业务系统中一个功能,一般属于「新契约」模块),你该如何开始设计工作?以及怎么做设计工作?

DDD 的核心价值就是解决这类复杂系统的设计(至少它是这么宣称的),如果你能理解并掌握 DDD 的话,在面对一个复杂的业务系统需求时应该能够给出一个合理,可行,具备可维护性与扩展性的设计方案。是不是很值得期待?那么我们继续往下看,DDD 是怎么做到这一点的。

分层

「分层」是广大工程师最熟悉的架构模式之一,特别是对使用 Java 的 CRUD Boy 而言,分层架构是伴随着 Java EE 一路走来的。在 DDD 中也引入了分层的概念,只是与传统的三层架构不同,将中间我们一般称之为 Business Logic Layer 的那层分为了 Application 与 Domain 两层,如下图:

对于分层 Eric Evans 的书中给出了一些说明,首先每一层只能依赖自己以下那一层的服务,而不能调用上一次的服务。可以理解为层与层之间的依赖关系是单向的,是自上而下的。其次 Application 与 Domain 是实现业务规则的核心,它们不应该依赖于某个特殊的框架或是技术,在 Java 中你的 Application 与 Domain 应该由 POJO 实现(Plain Old Java Object, 即普通的 Java 对象)。

Eric Evans 的书中给出了一些指导意见,但并没有给出相关的参考实现,那么问题就来了,项目中到底怎么实现分层?有的人会说这还有什么难的,不就是按照层级建几个 package 吗?其实问题的核心是如何管理层与层之间的交互。一些简陋的做法就是通过划分 VO(Value Object) ,PO(Persistent Object) 在几个层之间进行传递,但这远不是合理的分层架构实现。

我们先来看一个分层的参考实现,"The Clean “Architecture” 即整洁架构:

当然还有其他类似的分层架构参考,例如「六边形架构」(Hexagonal Architecture),「洋葱圈架构」(Onion Architecture) 等。接下来需要解决的就是如何在项目中实现这样一个分层架构了。

由于篇幅的原因我会在下一篇中介绍 Clean Architecture 的代码实现,同时也会介绍在实现分层架构中所遇到的问题以及解决的方法,希望你不会错过。

欢迎关注我的微信号「且把金针度与人」,获取更多高质量文章