如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念
一、前言
DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了。自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平台也算是对DDD的推广尽了一份绵薄之力。一开始接触这个东西是在2014年,真的觉得像是发现了一片新大陆一般,对我整个程序开发视野有了新的理解,但是像[Vaughn Vernon]《实现领域驱动设计》里写的那样,景色虽好,可是自己很长一段时间内很混乱,理不清眼前的陌生世界,因为它与传统的观念完全不同。我相信大部分同学刚接触DDD的时候也会有一样的感觉。
这次开始写这系列,也希望有越来越多的人能够加入到DDD的队列中,在我之前,园子里的netfocus、dax.net、田园里的蟋蟀等园友都已经对推广DDD做了很多事情,再此感谢下各位,这些分享在我学习DDD的道路上给予了很多帮助。
本次系列中多处引用[Vaughn Vernon]《实现领域驱动设计》一书里的语句,我认为此书可以作为各位进入DDD的敲门砖,希望大家能够去拜读一下此书。
二、名词解释
首先我觉得有必要先把DDD中的常用名词做一个解释。
界限上下文:代表一个系统、一个应用程序或者一种业务服务。限界上下文所包含的领域模型概念应该恰如其分,不多也不少。
通用语言:作用于某个“限界上下文”,在一个特定的限界上下文中只使用一套通用语言,并且保证它的清晰性(避免一个概念在同一个界限上下文中的二义性)和简洁性。举个例子:像京东和天猫这样的B2C系统中会用到系统的人有2种,买家和卖家,对于系统来说都可以称为用户,但是这样破坏了清晰性的特点。如果使用一个类似Type的枚举来区分,破坏了简洁性。所以对于这种场景,就应该直接设计2个对象:买家和卖家。
领域:从大了看,领域代表整个公司的运作一切。从小了看,是每个组织运作中的一切。所以领域的概念必然与公司的组织架构所承担的职责有一定的关系。
子域:一个领域内可以包含1个或者多个子域。理论上一个子域对应一个限界上下文是最优也是最理想的情况,但是有时又要考虑到业务关联度需要做出权衡。子域又分核心域、支撑子域、通用子域。
核心域:它是整个业务领域的一部分,也是业务成功的主要促成因素。从战略层面上讲,企业应该在核心域上胜人一筹。我们应该给予核心域最高的优先级、最资深的领域专家和最优秀的开发团队。在实施DDD的过程中将主要关注核心域。
支撑子域:对应着业务的某些重要方面,但却不是核心,那么它便是一个支撑子域。
通用子域:某个支撑子域的运用范围是整个系统,那么这个子域便是通用子域。
上下文映射图:由多个界限上下文和子域组成的表示当前单个领域或者多个领域之间的集成关系图。
三、实施DDD的关键
我认为实施DDD最最最关键的东西有2样。
一是“通用语言”,没有基于通用语言建立的所谓的聚合,实体,值对象,只能算是DDDLite,只是技术层面的一种设计方式。
二是“建模”,建模又分为战略建模和战术建模,这2者相辅相成,来构建合理的上下文映射图。
①战略建模:战略建模是以一种最宏观的角度去审视整个项目对它进行拆分,来划分“界限上下文”,最终形成一个具有俯瞰视角的“上下文映射图”。
②战术建模:在我们战略建模划出的“界限上下文”中进行“聚合”、“实体”、“值对象”的建模,并且按模块分组。
四、如何构建一个领域的上下文映射图
对于如何构建一个上下文映射图,分为思想和操作2个层面。
首先思想层面需要引入2个空间的概念:问题空间和解决方案空间。
在问题空间中,我们思考的是业务所面临的挑战,而在解决方案空间中,我们思考如何实现软件以解决这些业务挑战。
评估问题空间和解决方案空间的问题:
①这个战略核心域的名字是什么,它的目标是什么?
②这个战略核心域中包含哪些概念?
③这个核心域的支撑子域和通用子域是什么?
其次操作层面就是[Vaughn Vernon]《实现领域驱动设计》里提到的9种组织模式和集成模式。
①合作关系(Partnership):如果2个限界上下文的团队要么一起成功,要么一起失败,此时就是这种关系。应该为相互关联的软件功能制定好计划表,这样可以确保这些功能在同一个发布中完成。
②共享内核(Shared Kernel):对模型和代码的共享将产生一种紧密的依赖性,对于设计来说,这种依赖性可好可坏。我们需要为共享的部分模型指定一个显式边界,并保持共享内核的小型化。共享内核具有特殊的状态,在没有与另一个团队协商的情况下,这种状态是不能改变的。我们应该引入一种持续集成过程来保证共享内核与通用语言的一致性。【简单的说就是数据库共享】
③客户方——供应方(Customer-Supplier Development):当2个团队处于一种上游——下游关系时,上游团队可能独立于下游团队完成开发,此时下游团队的开发可能会受到很大的影响。因此,在上游团队的计划中,我们应该顾及到下游团队的需求。
④遵奉者(Conformist):在存在上游——下游关系的2个团队中,如果上游团队已经没有动力提供下游团队之需,下游团队便孤军无助了。处于利他主义,上游团队可能向下游团队做出种种承诺,但是有很大的可能是:这些承诺是无法实现的。下游团队只能盲目地使用上游团队模型。
⑤防腐层(Anticorruption Layer):在集成2个设计良好的限界上下文时,翻译层可能很简单,甚至可以很优雅的实现。但是,当共享内核,合作关系或客户方——供应方关系无法顺利实现时,此时的翻译将变得复杂。对于下游客户来说,你需要根据自己的领域模型创建一个单独的层,该层作为上游系统的委派向你的系统提供功能。防腐层通过已有的接口与其他系统交互,而其他系统只需要做很小的修改,甚至无需修改。在防腐层内部,它在你自己的模型和他方模型之间进行翻译转换。【为每个防腐层定义相应的领域服务】
⑥开放主机服务(Open Host Service):定义一种协议,让你的子系统通过该协议来访问你的服务。并且需要将协议公开。
⑦发布语言(Published Language):在2个限界上下文之间翻译模型需要一种公用的语言。此时你应该使用一种发布出来的共享语言来完成集成交流。发布语言通常与开放主机服务一起使用。
⑧另谋他路(SeparateWay):在确定需求时,我们应该做到坚持彻底。如果2套功能没有显著的关系,那么它们是可以被完全解耦的。集成总是昂贵的,有时带给你的好处也不大。声明2个限界上下文之间不存在任何关系,这样使得开发者去另外寻找简单的、专门的方法来解决问题。
⑨大泥球(Big Ball of Mud):当我们检查已有系统时,经常会发现系统中存在混杂在一起的模型,它们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之列。在这个边界内,不要试图使用复杂的建模手段来化解问题。同时,这样的系统有可能会向其他系统蔓延,应该对此保持警觉。
五、构建我们的上下文映射图
本次的系列的主题是电商网站,那么现在开始构建一个电商网站的上下文映射图。
①这个战略核心域的名字是什么,它的目标是什么?
销售核心域,目标是卖更多的商品,获取更多的利润。这点是整个组织的共同目标,所以应该很容易界定,并且应该是在整个组织中能最终够达成一致的某个观点。如图1。
【图1,点击图片查看大图】
②这个战略核心域中包含哪些概念?
我一般会使用“划分”,“组合”,“梳理”的3步走方式去做。先根据我们对这个领域的理解进行划分出各个上下文,然后重新审视每个上下文在销售这个领域中的定位来“圈地(划分子域)”,最后再思考是否能够完全体现出每个子域的功能和职责。我的思路是这样的,先根据我自己的经验得出了图2。
【图2,点击图片查看大图】
我思考了一下,整个销售过程中,订单只是一个结果,一旦到达这个环节,其实销售的工作已经结束了,那么订单的相关业务其实不应该属于销售的子域内,所以把它拿出去,变为图3这个样子。
【图3,点击图片查看大图】
然后看了一下全图,销售上下文的概念太泛,无法得知其中应该干什么,怎么去销售,有哪些影响销售概念,我又做了拆分。如下图4。
【图4,点击图片查看大图】
到这里几个核心域内包含的概念已经出来了:价格体系(促销、会员价)、销售方式(打包、捆绑赠品)、内容管理、购买。这其中最核心的又是购买。
③这个核心域的支撑子域和通用子域是什么?
这里开始我们需要对我们整理出的各个上下文和子域结合起来,并且根据9种组织模式和集成模式表达出各上下文之间的关系。如下图5。
【图5,点击图片查看大图】
这里的U和D分别代表UpStream和DownStream,表达出上下游关系,并且图的位置也需要看出这点。另外OHS+PL = Open Host Service+Published Language,ACL = Anticorruption Layer。
发现这里的5个子域中,订单和物流子域分别承担着2个子域的下游支撑作用,那么成为了通用子域。
最终这个问题的结果为商品子域[支撑]、会员子域[支撑]、支付子域[支撑]、订单子域[通用]、物流子域[通用]。
最后再考虑到一些非必须的辅助性概念,数据分析系统和预测系统,形成我们的最终上下文映射图。
【图6,点击图片查看大图】
六、结语
以上的每个环节都应该尽可能有领域专家参与,这样才能得出最符合实际的上下文映射图。DDD之路不好走,并且从短期的表面来看,需要花费的时间和精力会比我们常规的数据驱动开发方式看上去多的多。但是从沟通的便捷性、理解的错误率、代码的可维护性来看,DDD能够让对项目的把控更上一个层级。并且为了应对互联网行业的快速变化,我们得到的远比我们付出的多的多。