我们前面已经完成了“群买菜”在“问题空间”的全局分析,本节开始进入“解空间”映射。我将用两节的篇幅来讲解“群买菜”的战略设计。首先,对战略设计的理论知识做一个浓缩性介绍;其次,分两节介绍“群买菜”的 DDD 战略设计,包括:本节介绍系统上下文定义、限界上下文识别;下节介绍限界上下文映射、系统分层架构、战略层技术决策。
在上节中,我提到“解空间”映射包含 3 部分的内容:战略设计、战术设计、代码实现。其中,战略设计主要包含 3 部分工作:
下面我就这 3 方面的工作先做个进一步的介绍(这种在实例演示之前,同步的简要介绍相关的 DDD 理论概念,是本专题的写作风格之一):
本系统的主要用户是 4 类:消费者客户、商家创建人、商家授权操作人、平台运营人员。本系统前端是运行在微信环境里的小程序,会使用到微信支付系统执行相应的支付功能、会使用微信公众平台发送消息提醒、会使用短信平台发送验证码和消息提醒、会使用腾讯云存储上传图片文件、会使用腾讯地图帮助用户定位。因此“群买菜”系统上下文如下:
根据张逸老师 DDDUP 的建议,限界上下文的识别,主要采用如下图所示的 V 型映射工作法:
在前面全局分析章节,我们已经完成了 V 型映射的前半部分:从业务流程到业务服务(即业务用例)。本节战略设计部分,主要需要完成 V 型映射的后半部分:从业务服务(业务用例)到限界上下文。我们将通过如下的 4 个步骤来得出最终的限界上下文划分及其映射关系:
1.首先,我们从语义相关和功能相关两个角度对业务用例进行归类和归纳,得到业务主体,作为限界上下文候选项;
2.其次,我们按照这些领域概念的亲密度和知识语境对归类归纳后的业务主体进行调整,作为限界上下文;
3.再次,我们还要基于如下 3 个原则对限界上下文进行检验,并在适当时进行调整。
4.最后,我们根据系统上下文的边界、以及其它的一些技术实现因素,对限界上下文进行适配。因为限界上下文最终一定是解空间的设计方案,所以这是一个跳不过去的过程。
在按照以上 4 个步骤分析的过程中,我们需要注意的是:我们要始终考虑限界上下文的主要 4 个设计特征:最小完备、自我履行、稳定空间、独立进化。简单点说,就是单一职责原则:不应该有两个以上因素引起某个限界上下文发生变化。
下面,我们先来进行第一步,即根据语义和功能相关对业务用例进行归类和归纳,得到业务主体,作为限界上下文候选项。下图是我们做的第一步归类归纳后的业务主体识别结果:
其中,有 6 个业务用例我们暂时没有为其找到合适的业务主体归属:加商品到购物车、选购接龙商品到购物车、确认订单付款、确认接龙付款、管理客户信息、创建商家及账户。关于它们的说明如下:
接下来,我们继续进行第二步的分析:根据业务主体之间的亲密度、知识语境进一步调整,找到限界上下文。在上一步的工作中,我们遗留下 6 个业务用例难以归类。下面我们来一一进行分析:
1. 其中“加商品到购物车”和“选购接龙商品到购物车”是因为按照一般电商系统的惯例,“购物车”应该归到“订单”上下文中,但考虑到现在有“订单”和“接龙”两个上下文,那这两个跟购物车有关的用例,到底应该归属“订单”还是“接龙”?还是两个用例分别归属一个上下文呢?实际上,我们仔细分析后,可以发现:其实“接龙”只是一种商品打包、且预定式销售方式,是一种“营销方式”,并不能和“订单”上下文在业务价值上同等对待。而“订单”上下文更多是各种营销方式下产生销售后的一系列行为。就好比,如果本目标系统还有“秒杀”、“团购”、“砍一刀”等营销方式,这些营销方式并不能和“订单”上下文并列,反而应该是“订单”上下文的调用方——因为每种营销产生的订单,都最终要使用“订单”上下文进行订单管理。所以,“购物车”这一领域概念从亲密度上来说,怎么也应该归属“订单”上下文。为此,我们做这样的处理:
a) 将“加商品到购物车”和“选购接龙商品到购物车”作为“订单”上下文的内容;
b) 并预计在将来的战术设计中,会出现“购物车”这一聚合根实体对象;
2. “确认订单付款”、“确认接龙付款”这两个业务用例,我们深入分析其业务逻辑,事实上是在客户选定待购买商品、并放置到购物车后,在购物车的基础上做的操作。所以,其实这两个业务用例,可以合并为一个业务用例叫“确定购买并付款”。考虑到该用例与购物车的亲密度、以及只有“购物车”概念才具备其购买和付款所需的全部领域知识(“支付”上下文所需要的支付金额、以及商品列表和描述等,都来自于“购物车”),我们就应该将这个业务用例放到“订单”上下文去。
3. 关于“管理客户信息”用例,因为实在无法跟任何已有的业务主体合并归类,故应该大胆的就设立一个只有一个业务用例的业务主体“客户”。
4. 关于“创建商家”业务用例,我们继续留到下一步分析。
经过第二步的分析,我们调整限界上下文识别如下图:
我们再进行第三步的分析:基于如下 3 个原则对限界上下文进行检验,并在适当时进行调整。包括:正交原则、SLAP 原则、奥卡姆剃刀原则。同时,我们在这一步,再回顾限界上下文设计的 4 个特征:最小完备、自我履行、稳定空间、独立进化。下面是分析过程:
1. “创建商家”这个用例看起来貌似应该有个业务主体“商家”、也貌似可以归类到业务主体“账户”。我们分别考虑两种方案:
a) 我们首先考虑方案 1:新增业务主体“商家”。如果我们这么做,就存在违背 SLAP 原则(单一抽象层次原则)的风险。因为“商家”业务主体,看起来也应该包含“加盟”这个限界上下文,这就必须将“商家”和“加盟”合并后,成为新的限界上下文“商家与加盟”。但其实,这是有问题的。因为,目标系统里面的“加盟”其实是“店铺”之间的加盟关系,这考虑的话,“加盟”就应该和“店铺”、“商家”这 3 个上下文合并起来统一叫“商家”。但是这样的话,就又上下文设计的违背了单一职责原则:这一个上下文会可能受到 3 个独立影响因素的变化而变化:既可能在将来因为平台方对“商家运营”规则的调整而变化,也可能因为加盟本身业务规则的变化而变化,还可能因为店铺管理的业务规则变化而变化。
b) 其次我们考虑方案 2:将“创建商家”放到“账户”上下文。我们是根据商家创建人来创建“商家”并进而创建“商家账户”的,并且“商家账户”一定是一对一绑定到“商家”的,从亲密度上来看,确实“商家”和“账户”的关系更为亲密。但另一方面,将“商家”合并到“账户”上下文,却又存在违背单一职责的风险。
c) 事实上,从目前“商家”需要实现的业务行为来看,只有一个“创建商家”行为,并没有太多的其它领域知识需求。从这个角度来说,我们将其暂时放在“账户”上下文一起,是可以接受的。况且,如果按照奥卡姆剃刀原则,我们在难以抉择一个业务主体要不要独立上下文时,就先不独立。所以,从总体上来说,我们倾向于选择方案 2:将“商家”合并到“账户”上下文,合称为“商家和账户”。
d) 同时,事实上“创建商家”和“创建商家账户”在目前目标系统的实现中,因为业务知识很单一,它们总是同时出现的,所以我们干脆将“创建商家”和“创建商家账户”合并为“创建商家和账户”一个业务用例。
2. 其次,我们再来看“发送短信验证码”用例,因为涉及到跟伴生系统短信平台的接口,这个用例看起来可以放到一个独立上下文中,也可以作为具体调用该用例的上下文(目前只有店铺)的防腐层 ACL 存在。但事实上,我们再次通览到目前为止的上下文识别、及其内包含的业务用例,还发现一个问题:“发送订单提醒”属于“订单”上下文,但实际上这个业务行为的变化因素,很可能是我们采用提醒的技术设施扩展、以及技术设施本身的业务规则变化而引起的变化,这和“订单”上下文本身会因为订单管理规则变化而变化,其实是两个不同的影响因素,违背了“单一职责原则”。因此,我们需要将其独立出一个新的出来。所以,“发送短信验证码”、“发送订单提醒”可以考虑放到一个“消息集成”上下文去。
3. 再次,我们再来看业务用例“获取微信绑定手机号”。这个用例看起来跟“发送短信验证码”类似,可以作为某上下文的 ACL 存在、也可以做为独立上下文存在。如果作为 ACL,就因为有“订单”和“店铺”两个上下文都用到,会出现重复实现的问题。因此,优先选择作为独立上下文。但是其做为独立上下文又过于孤立,因此综合考虑后,可以和上段提到的“消息集成”上下文合并,取名为“平台集成”上下文。从其职责上来说,“平台集成”这个名称也是合理的,因为其中涉及到的内容,全部跟微信平台有关(公众号信息、微信绑定手机号、短信平台也可以用腾讯云自带的短信 SDK)。这还暗示着:如果把目标系统移植到支付宝小程序、抖音小程序等,只需要调整“平台集成”上下文的实现即可。
4. 最后,我们看“加盟”这一上下文,其实从业务角度来说应该是和“店铺”分开的。但鉴于现在系统提供的加盟管理功能很弱,只是加盟政策和店铺之间加盟关系的管理。这就会导致一个问题:到底是“加盟”独立出来一个上下文呢?还是和“店铺”上下文合并为一个上下文?根据奥卡姆剃刀原则,当我们难以抉择是否需要独立限界上下文时,我们就执行“如无必要、勿增实体”原则。为此,对于这种犹豫纠结的情况,我们就将“加盟”和“店铺”上下文合并为一个。另外,这两个上下文合并后,我们还发现业务用例“搜索品牌店铺”其实是“添加品牌店铺到加盟列表”用例的一个步骤,因此我们将这两个业务用例也合并。
经过第三步的分析,我们调整后的限界上下文列表如下图:
我们还有最后一步分析:根据系统上下文边界、以及技术实现因素,对限界上下文的识别情况进行最终的确认。从本目标系统的系统上下文情况来看,目前技术因素我们能看出来,如下 3 方面需要考虑的因素:
1. “支付”上下文其实就只能是微信支付提供的能力,这个上下文不属于我们目标系统的边界,而属于伴生系统,故应该删除。
2. 我们还使用“腾讯地图”这一伴生系统对用户的手机位置进行定位,但该功能目前主要用于两个地方:
a) 用于“店铺”上下文中帮助用户自动定位最近店铺;
b) 根据产品经理的 UI 设计,在“查看店铺详情”页面上需要为用户提供地图导航功能,让客户能够找到商家的线下店铺;
c) 在可预期的将来,也不会有更多的上下文会使用腾讯地图功能。为此,就不设立独立的、可作为统一对接上下游系统的“定位导航”上下文了。
3. 另外,按照常规系统建设需要,在其它一些系统建设中,诸如报表统计模块可能会引起 ETL/大数据之类的上下文识别、以及秒杀等特殊性能并发要求引起的特定上下文等,是需要考虑进来的。但我们这个目标系统目前比较简单,没有涉及到后台的数据分析统计、也因为是产品早期阶段不考虑“秒杀”等特殊性能要求的业务场景,故不做这方面的设计。
在真实的大型项目中,还要考虑项目团队的能力和边界问题——“康威定律”,你不应该让一个“限界上下文”被拆分到多个开发团队去负责、而只能让一个团队负责多个“限界上下文”。由于“群买菜”系统完全有本人独立开发,所以这里就不相关了。
最终,我们的限界上下文识别结果如下图(为了更清晰的表达,已省去上下文内部的业务用例):
本节文章就到这里了,下节文章中,我将完成“战略设计”中剩下的工作:限界上下文关系映射、系统分层架构设计、战略层技术决策。
领取专属 10元无门槛券
私享最新 技术干货