DDD上下文映射

点击↑上方↑蓝色“编了个程”关注我~

761022ab17dce6d22c1ea2f226285300.png

这是Yasin的第 52 篇原创文章

7b929177e2b07bc0efb7118f131a3295.png

Y说

hi,失踪人口回归了。还记得上次发文章,是在上次。

最近有点小忙,状态也有些低迷,脑子空空的,感觉精力不是很充沛,也不是很想学习新东西。

被热心网友催更了几次,自己也觉得应该再把状态捡起来,毕竟人生如逆水行舟。

还是要多看多学,偶尔产出一点自己学到的东西或者思考,用输出倒逼输入,持续成长。

希望与诸君共勉。

什么是上下文映射

上下文映射,对应的英文单词是Context Map,代表的是领域驱动设计中,多个限界上下文之前的关系。方便设计者和开发者能够一目了然地看到每个限界上下文和其它限界上下文之间的关系,最终的产出可能是一张映射图,或者映射卡片。

下面的一些限界上下文映射设计只是一种参考。实际上的限界上下文映射的设计,不只是跟设计决策和技术实现有关,还跟企业文化、组织架构有关。

有哪些上下文映射

分离方式

分离方式(separate way),也有的资料翻译为“各行其道”。分离方式指的是两个限界上下文没有任何关系,没有关系其实就是一种非常好的设计,因为它们可以独立变化,互相影响。

但在实际的开发过程中,可能两个限界上下文会有一些耦合。如果设计者认为这两个限界上下文解耦的价值远远大于复用的价值(比如分属于两个差异很大的团队),那可以通过引入少量的重复来彻底解耦开来。

比如:在电商场景中,支付上下文,就和库存上下文没有任何关系。

ea92d913434e56929e88c4fc144b9e2e.png

客户-供应

客户-供应(customer/supplier)是我们最中间的一种上下文映射方式。一方提供服务,另一方去调用服务。我们类比水流,上游发生变化可能会影响下游,所以我们把提供服务的一方称为“上游”,使用服务的一方称为“下游”。这与调用关系刚好是相反的。

比如:在电商场景中,订单上下文依赖库存上下文,所以库存上下文就是订单上下文的上游。

6593ad3a278f09613507e0dfe45f7535.png

发布-订阅

发布-订阅(publisher/subscriber)也是一种很常见的上下文映射方式,在实际的开发过程中往往是通过消息中间件来实现。这个模式并不在Eric Evans提出的上下文映射模式里面,但随着事件慢慢成为领域驱动设计中的“一等公民”之后,发布-订阅模式并普遍用于处理限界上下文之间的协作关系。

发布-订阅模式源自于设计模式中的“观察者模式”,上下游通过消息去通信,下游注册观察者,上游作为发布者,如果上游发生了变化,会发布一个业务事件,下游收到这个事件后进行后续的操作。

可以看到,发布-订阅模式与客户-供应模式最大的不同,在于发布-订阅模式,是上游主动发起业务的变化,而不是被动等下游去调用上游。它相较于客户-供应模式而言,耦合程度会低一些。

比如:在电商场景中,订单上下文和物流上下文,就可以通过发布-订阅模式来做。订单完成后,发生订单完成事件,物流上下文监听事件开始物流配送。

4b77da4d52e0b81fe48c75f96ef465bb.png

开放主机服务和发布语言

开放主机服务(open host service, OHS),指的是上游提供一些公开的服务,包括它们的通信方式、数据格式等,并且承诺这些服务不会轻易做出变化。

发布语言(published language)通常和开放主机服务一起配合使用,主要用于两个限界上下文之间的模型转换。

其实我在看这方面的资料的时候,对开放主机服务和发布语言还是有一些疑惑,很多资料都是对它们一笔带过,无法了解更多的细节。开放主机服务和客户-供应有什么区别?发布语言到底是什么?

在我看来,开放主机服务和客户-供应最大的区别在于,它承诺了服务不会轻易变化。那下游服务就可以不用做专门的防御措施来抵抗上游的变化(也就是下面会介绍的防腐层)。

而发布语言在开放主机服务上,本质上就是开放主机服务定义的协议、request、response、服务名唯一名字等。因为开放服务不可能将上下文内部的领域模型暴露出去,所以会需要对外定义一些数据传输模型(DTO)来提供服务。

在电商业务中,财务系统就是一个相对稳定的业务,可以高度抽象为几个原子的财务操作,比如:资金的申请,占用,扣减,核销等。

252cc5918d61be0a2644e9c7150f4d31.png

防腐层

防腐层(anti corruption layer, ACL)是应对上游服务变化的利器。尤其是当下游限界上下文有多个地方依赖某一个上游时,一旦上游服务发生变化,下游服务如果不做防腐措施,就会面临大面积的修改。

af4d36dafdf833ed74b12be2a4832901.png

如果上游限界上下文存在多个下游时,倘若都需要隔离变化,每个下游都做防腐层成本比较大,可以考虑单独抽一个只有防腐功能的限界上下文,避免代码重复。

在电商业务中,可能订单上下文、售后上下文都会涉及到支付功能,如果支付功能是对接了大量的第三方支付,每个上下文自己去做防腐层就会有一些代码重复。那可以把支付上下文单独抽出来做为一个上游的防腐层。

在面对“大泥球”一样错综复杂调用关系的老系统中,防腐层就是可以才帮助遗留系统迁移的利器。

4a36ac642f3766328959449081e56186.png

遵奉者

前面也提到了,有时候限界上下文之间关系的设计,还会受到企业文化和团队协作的影响。当上游服务不积极响应下游服务的需求时,会有三种方式来解决:

  • 分离方式:下游服务切断上游服务的依赖,自己来实现

  • 防腐层:复用上游的服务,但领域模型由下游团队自己来开发,然后用防腐层实现上下游领域模型之间的转换。

  • 遵奉者:严格遵从上游团队的模型,以消除复杂的模型转换逻辑

遵奉者(conformist)就是一种妥协,当下游团队选择遵奉上游团队设计的模型时,意味着它对上游产生了模型上的强依赖。

36efc0a7b517a478364c6811e0d19d2f.png

Eric Evans的观点是,限界上下文之间的模型复用是很危险的,如果不是因为重复开发的成本太高,应该尽量避免遵奉者模式。

比如在电商场景中,财务上下文是一个比较稳定的业务,在电商活动立项的时候,可能需要申请一笔预算,但这笔预算应该是有一个有效期的,也就是活动的起止时间,活动结束后是不能使用预算的。但财务团队拒绝为了活动这个特殊的场景,在他们的上下文内部的领域模型增加“预算有效期”这个字段。

活动上下文可以选择上面的三种方式之一来解决这个问题:

  • 可以自己实现一套财务模型,

  • 复用财务上下文的占用、扣减等服务,防腐层转换为活动上下文内部的带有预算有效期的内部模型;

  • 遵循财务上下文的模型。在其它上下文(比如流量投放上下文)使用预算的时候,再通过通过调用活动上下文去校验活动时间。

共享内核

共享内核(Shared Kernel),指的是将一个限界上下文将自己的领域模型暴露出去,给其它的限界上下文使用。共享内核不能像其它的限界上下文那样,自由地更改,但共享内核也会造成耦合。因此我们只可能把那些非常稳定且具有复用价值的领域模型封装到共享内核上下文中。

共享内核通常以库的形式(比如Java的jar包)被其它限界上下文复用,它本身不提供远程服务。所以可以理解为它是一种特殊的进程内通信。

耦合的代价是巨大的,笔者个人不是建议使用共享内核这种模式,除非真的重复的代价远远大于耦合。

合作者

合作者(partnership),指的是两个或多个限界上下文彼此依赖,联系紧密。具体表现出来的可能就是循环依赖,两个限界上下文形成了强耦合关系。团队之间的良好协作是好事,但强耦合会带来一系列的问题。要解决这种强耦合,通常有三种方式:

  • 合并:既然分不开,说明当时拆分得可能不合理,他们本质上也许可以合并为一个限界上下文。

  • 重新分配:理清楚为什么相互依赖,尝试把一些功能或服务重新分配,尽量减少上下文之间的依赖。

  • 抽取:如果实在不能合又分配不清楚,那可以考虑重新抽取为一个新的限界上下文,然后之前的两个限界上下文去依赖这个新的限界上下文。

如果上述几种方法都不使用当前的团队和场景,那就只能允许合作者模式存在了。需要在限界上下文中特别标识出来,这里存在高耦合,变动会比较容易引发风险。

1a1ff968538bab742ef6ba9de6b47356.png

总结

如果说我们在上下文映射设计时,要尽量做到低耦合,那分离方式、发布-订阅、客户-供应防腐层是比较推荐的模式。

而遵奉者、共享内核、合作者是需要尽量去避免的。避免的方式大多都是重复或者冗余,这个时候就要去衡量是否值得了。

软件设计就是这样,可能没有完美的方案,总是在各方面权衡利弊得失,有所取舍,最终力求得到一个最优的解决方案,这就是软件设计很难的原因,也是它的魅力所在。

任何决策都应该考虑收益和成本,只有收益大于成本,决策才有可能是合理的。

虽然是限界上下文之间的映射,但其实落地下来,咱们不使用领域驱动设计的微服务拆分也可以参考这几种模型,尽量使微服务做到“高内聚,低耦合”。

e98f3ea87506b914344cdb7d6887e9bf.png

关于作者

我是Yasin,一个爱写博客的技术人

微信公众号:编了个程(blgcheng)

个人网站:https://yasinshaw.com

欢迎关注这个公众号116e93aea022278ceb2ac83d2ec72f2d.png

27a6901d4a42dbff499a182f25f79e65.png

968be17716622544b46262ad939729e7.png

数据源切换的一般步骤


b8ed267c85b132d8bddd9c14eb3529b1.png

MySQL到底在RR层面解决幻读了吗?


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值