2.2.1 领域驱动建模介绍
传统的模型设计方法有以下几种。
拖曳式设计,例如Smart UI模式,把展示层、业务处理层、数据处理层等过程简单封装成固定可拖曳模块,用户只需要进行简单的排列组合即可完成系统建模以及业务逻辑开发。
业务过程式设计,例如Transaction Script模式,这种模式严格按每个业务流程走向来设计程序,这种面向过程的设计现在还在大量使用。
领域驱动设计(Domain-Driven-Design, DDD),这种方法按照领域边界来设计建模。DDD的概念其实早就出现了,相关的理论也发展多年,只是近年随着微服务的兴起而再次受到重视,重新活跃起来。
前两种建模方法在面对简单的、少租户的业务建模方面是非常方便快捷的,轻量而又高效率,但是面对复杂交叉业务,多租户、高并发、大数据量的系统就会显得单薄,就算勉强支撑起应用,后期维护也将非常耗时耗力。
从微服务的发展历程及设计思想来看,业务的微边界设定、服务化的拆分与DDD的限界上下文、聚合等建模思路是一致的,因此本书推荐DDD这种业务视角的设计方法。
DDD建模典型步骤如图2-4所示。
1)事件风暴完成所有事件流程分析。
2)找出事件流程中的实体与值对象。
图2-4 DDD建模典型步骤
3)将实体与值对象组合成一个完整的聚合。
4)确认聚合的聚合根。
5)确认聚合与限界上下文的对应关系。
6)根据限界上下文划分微服务。
我们先梳理一下本书涉及的一些概念,后面结合落地情况进一步解释。
1.业务描述方面涉及的概念
(1)实体
参与业务流程的各主体单元,常常结合核心场景分析、产品生命周期、用户旅程等辅以事件风暴来抽取,例如购票业务中参与的实体有客户、代理、订单、机票、航空公司等。
(2)领域
业务流程中实体与实体之间的关系网,例如客户实体和订单实体是1:n的关系,两者所构成的关系还会包含其他实体及它们之间的关系,这个巨大的关系网组成了一个领域,例如销售域等。领域还可以划分为核心域、支撑域和通用域等子域,例如销售域可进一步划分为购买域、出售域、价格域等。
2.DDD解决方案方面对应的概念
(1)限界上下文
在领域中围绕某一个实体,以其为中心,再加上其描述信息、功能信息和由一定的外界联系信息组成的上下文相关环境信息等一起组成了这个实体的限界上下文。例如客户实体,包含其自身的属性描述、功能标签,与地址实体这个外界联系等共同组成了“客户”这个限界上下文。当然,这里面要注意的是,上述领域中的子域与限界上下文不一定是一一对应的关系。
(2)聚合与聚合根
限界上下文中的实体之间可能存在强相关的关系,例如订单和订单项,那么订单和订单项就组成了一个聚合,而这个聚合的唯一标识符,或者说是外界沟通的“联络人”——订单就是这个聚合的聚合根。聚合作为限界上下文的组成单位,同时会作为存储入库的基本单位。
(3)值对象
首先,值对象也是实体,这一类实体在某个限界上下文中有特定的实例,不需要唯一标识符。当更改值之后会变成另外一个新的对象,例如订单中的地址信息、商品的颜色信息等。
但是,值对象在另外一个限界上下文中可以是聚合根,例如,商品在限界上下文中是值对象,但是商品在商品限界上下文中是作为聚合根存在的,即值对象与聚合根的身份在不同的限界上下文中是变化的,这点要理解清楚。
DDD不只是对业务逻辑进行抽象建模,也可以对后续的微服务拆分、数据库设计、RESTful API设计提供指导,具体如下所示。
(1)以限界上下文粒度进行微服务拆分
限界上下文是由聚合构成的,限界上下文中的聚合有比较紧密的依赖关系,如果在聚合的粒度上划分微服务,则微服务之间的耦合性过高、内聚性不够。例如在商品限界上下文中把规格与颜色等聚合设计成微服务显然是设计过度的体现。
(2)以聚合粒度进行数据库设计
DDD中的聚合是最小粒度的业务单元,其具有完整的业务描述,而且关键是每个聚合都有一个聚合根,这和数据库范式设计中的唯一标识符不谋而合,所以一个聚合至少可以对应一张表。
(3)RESTful API设计
REST以资源为中心,这些资源通过URI来确定,我们使用HTTP中的动词+资源名称来使用REST。而DDD中强调的是先弄清楚资源的处理逻辑,再在虚拟世界中建模,对实体资源的处理显然要比HTTP的CRUD动词要复杂得多。那这两者的理念如何结合呢?答案就是在HTTP动词+资源名称组成的URI后面还需要加入业务动作,例如PUT/account/close、PUT/account/reopen。对账户的更新所包含的业务操作不再局限到函数中执行,而是直接在API上体现业务,这大大提升了设计的友好性及程序的可读性。