复杂场景的最终解-边界上下文
立个非常大的标题,是因为找到了复杂业务的最终解法,领域模型之边界上下文!纯实践例子。
这里先抛出本文最核心的观点:所谓的场景就是领域边界上下文!
以电商业务中复杂的营销优惠场景来举例!
在营销优惠场景中,优惠的方式特别多,优惠的种类也特别多,涉及到的点也特别多。无论有多少,随着时间的推移,堆叠的业务会越来越多,如果没有好的设计,合适拆分。后续的业务将非常臃肿,无法进行快速迭代或者牵一发而动全身!
下面提出复杂业务的最终解法,使用领域驱动模型-边界上下文应对复杂场景。将在这里说明业务如何一步一步发展,系统如何一步一步成长。长大后,又是如何一步一步拆分!往大了说为什么大中台为何不合适。往小了说,为什么简单业务更适合MVC!
这里将用电商平台的真实的优惠业务场景举例,如何一步一步落地!
遇到的问题
我们在设计一个业务时,常常是通过单一场景设计的。比如一个商品的价格在详情页显示是券后价。指定用户在分享时,又要需要按原价显示,原因是并不是所有用户都有券。分享时,只能按原价分享。计算方式一样,只是显示方式不一致,不同场景计算方式不一样是需求中最常见的。为了非常常见的这种变化,我们总结出了新的方法用来解决复杂问题。
领域模型划分
优惠工具对象(模板对象)
先建领域对象,这是一个简化了的领域对象,在后台创建一个优惠活动的时候,填写优惠信息
这里先不用在意哪些是领域对象、值对象、聚合对象,现在展示在你面前的是业务想要表达的真实的业务!业务想这么干,他的逻辑能讲得通,那这个系统就能做得出来。
优惠信息对象(实例对象)
如优惠券,是指用户真实领取到了的优惠券!
这里单品券、多品券都是领域对象。优惠券、优惠价是抽象类。有共性的都用抽象。
以上只是定义领域对象,结构图一画,哪些是聚合对象,哪些是领域对象,哪些是值对象就能很容易区分出来。
领域上下文
领域上下文在许多文档里解释都非常费劲,为了规避这个费劲的理解,还曾写过一篇关于领域关系模型的思考,用来阐述领域上下文其实还有更好的解。现在想想,那个可以做为领域模型的补充。
现在就来看看领域驱动模型中最核心的思想,领域上下文!
领域上下文(Bounded Context): 是领域驱动设计中的核心概念,用来划分领域模型的边界。在不同的上下文中,同一个领域对象可能呈现不同的行为和状态。
橘生淮南则为橘 生于淮北则为枳!
都是橘子,只是因为生的地方不同,味道差别大,而命名不同!
所以,影响这个橘子名称的是什么?某些属性值不一样!
那么橘子是个领域对象!淮南、淮北就是两个上下文。在淮南的上下文中,有个查询橘子名称的方法。在淮北的上下文中,也有个查询橘子名称的方法。而影响橘子名称的属性有比如地域、光照时长、甜度等等。
回到电商业务中,我们计算优惠的时候,有许多场景。比如,在购物车加购时、在结算页计算优惠时、立即购买页直接结算时都需要计算商品的优惠信息。
还有一些地方,比如商品卡片、商品详情、个人中心、优惠中心等都需要有相应的优惠展示。在系统做大时,这里的每个场景都可能是不同的人负责。到后面还有不同的业务方接入,总之,系统都要支持!
购物车上下文
绿色框表示这个上下文的核心功能方法
可以看到购物车上下文里边只有一个优惠上下文和一个商品列表。因为购物车里只有商品列表信息。
由业务逻辑可知,加购商品的时候,会计算这个商品的优惠价。我们把计算流程设定好!
- 查找优惠
- 判断优惠适用商品
- 优惠重叠优先级排序
- 算价
这里优惠由优惠券和优惠价2部分组成。所以,只需要问优惠上下文,这个商品有哪些优惠,优惠上下文只需要找出当前用户拥有的券,和这个商品适用的优惠价返回就行了。每个人只做自己领域内的事。
在优惠上下文里,还有一个换购上下文。换购一般指买指定的商品,可以低价购买另外一个商品或是赠送一个赠品。在购物车场景中,换购是一个常用场景。在这里只需要把商品给到换购上下文,让它告诉我当前购物车里有没有换购活动!
商品领域对象再提供一个减价方法。这样,商品的优惠价就出来了。
算价上下文要是算出有赠品,则直接把赠品返回就结束了。
结算页上下文
在结算页中,用户还可以选择是否使用自己的余额、权益来抵扣实付金额等
结算页上下文逻辑不同于购物车上下文。那么优惠明细上下文就可以再分为购物车优惠明细上下文与结算页优惠明细上下文,原来的优惠明细上下文就可以是个抽象类提取出来。
如果在算价上也有差异,也可以再单独区分出不同算价的上下文。
再来补充一个上下文
个人中心上下文
在个人中心的页面里,对应4个功能点
优惠券、购物卡、省钱卡、余额
刚好可复用原来在购物车中的使用过的这几个上下文。所以,新功能几乎都完美复用了!
每一个上下文都保持着非常清晰的边界,上下文中都有对应领域对象数据交换。核心关键点就是边界上下文划分,这里从一个复杂的系统入手,让你感受到边界上下文带来的优势。
原来的mvc模型也是这么做的,有什么不一样吗?
mvc模型多数情况下是会划分出来一些类似的”领域“服务来做一些数据集中,但仅仅只是提供查询功能。做得好点的,会抽象出来一些共性查询。实质上还是等同于定制开发。因为它的核心业务没有领域化,整个服务还是做的数据拼装。业务不高内聚,模块化就是虚的。
领域对象是一个纯对象,没有任何的IO操作。边界上下文也可以理解是个边界对象,它用来描述当前场景的上下文。因为边界上下文只依赖于领域对象。它理论上也是可以高度复用的,不能复用就新建个上下文,引入领域对象即可!
充血模型
充血模型: 指领域模型中不仅包含数据,还包含业务逻辑,和“贫血模型”形成对比。
领域对象与边界上下文一定是充血模型,边界上下文相当于是阿里标准规范里的manager层,让service层提供数据源,在上下文中构建场景数据,所有的计算都是领域对象自己计算,它相当于是个协调者,把领域对象串起来!做到代码真的是用人的思考方式写的。
设想两个场景,用户在购物车加购时需要计算优惠价。在结算页的时候,除了计算优惠价外,还需要计算用户的余额抵扣权益!
这两个场景分别对应两个场景上下文,对于代码来说,两个场景如同组合积木一样简单!甚至在结算页场景下,直接复用购物车算价场景的逻辑,而在这两者之间通用的部分其实就可以抽象出一个逻辑,比如叫算价上下文!
现实中的业务场景可能很复杂,复杂的它们有时候像是有共性,有时候各自又完全不搭边,到一定时间或场景时,它们又好像有共性了,怎么样才能处理好这种没边界的问题了。
边界上下文就是用来解决这种问题的。有边界时,它们可以抽象通用。没边界时它们各自完成自己的场景功能。待有新场景出现时,有共性的边界再抽象。这不就解决了无边界的复杂了!
微服务拆分
在业务拆分时,以拆分边界上下文是最合适,它描述是业务场景下,完成这一业务所需要的领域支撑!
举例说明
个人中心里的四个场景:优惠券、购物卡、省钱卡、余额
它们都属于优惠领域的,在不同的边界上下文中,它们数据相对较稳定,功能又是相似的。符合高内聚低耦合原则,可以拆分出微服务用来单独支撑业务。
如果业务被拆出去,原来的上下文可以沿用,只是原来的边界上下文由直接引用变成了间接引用,这是符合拆分预期的。
还有,如果因为系统按功能拆分,比如用户大量使用省钱卡,基本上不使用购物卡。那么省钱卡与购物卡按业务拆分出来,每个服务配置的机器资源数按各自承受的业务匹配!
所有的业务全是搭积木一样组合拆分。并且系统像有生命一样,它是慢慢长大的,如果业务成长,业务可以继续拆分,系统也可以很简单的拆分,如果业务收缩,关闭某块功能也可以很简单!
领域专家一定要懂业务,贴着业务干活,并能够非常有良好的沟通理解能力!特别是自己要站在业务一方引导业务理解系统。
复杂源于多,解决复杂的根本就是拆分够细,内聚够强,才能够有丰富的组合应对复杂!说到底还是高内聚低耦合。