微服务时代的DDD:实现领域模型

书接上文

上次说到,我们看到DDD和微服务是如何两情相悦的,直到深入到实现的粒度时,我们发现没这么乐观。

微服务天生应该是:

  • 自包含的 (包含数据,更希望是领域模型)

  • 自部署的(任何一个微服务都应该是可独立部署的,不依赖任何兄弟产品的部署)

领域模型

如此来讲,在DDD中,我们经常看到领域模型本身被融合成一个或多个可部署的构件,如Java JAR或者NuGet包。现在,这个可以是一个包,也可以是几个,但是这不是我们这里担心的问题 (已经有大量的文章介绍把领域模型拆分成为包的最佳实践,这个话题另议)。

要解决的问题是,如果我的微服务在大多数场景应该是自部署的,怎么将这个通用领域模型代码库引入?我想让引用它的所有客户端都同时被更新。这会打破微服务自部署的规则,比如在项目里有4个微服务,然后每次我更新领域模型,就需要更新所有的微服务。

注:也不总这样,也可能是很小的变更或者只针对微服务X的特性的变更需要重部署。然而,我们想要保证,尽可能让微服务们保持领域模型的版本同步。

有什么可选项?

那我们怎么办呢?下面看一些可行的办法:

不用DDD

之前说过,再次重申:如果你不需要DDD的复杂性,不要用它!

DDD是复杂的。建模应用成为用心良苦的聚合,合适的组织的仓库和应用服务才是与之相配的。这种须用DDD的业务应用,本身应该就是很复杂的。如果你在构建一个简单的CRUD系统,除了对象创建、获取,更新和删除,没什么别的复杂逻辑,那就忘了DDD。那样会让你的生活清爽许多。然后,问题解决了 :)

如果你确实需要DDD,那往下看。

用老办法:保持你的领域模型自包含

保持领域模型隔离在一个自包含的通用集的包,是有意义的。但是这个明显制约了你,特别是如果你想保持领域模型版本一致,而它们是各自独立的部署的。然而,如果你不会经常大动领域模型,或者你打算每次发布让所有的微服务都更新领域模型,这就是个可选项。

如何能缓解这样的痛点呢?

  1. 一个健壮的蓝绿部署策略,结合健壮的功能测试用例可以帮助缓解领域模型变更引起问题/中断的可能性。蓝绿部署意味着你有一个新的版本,运行着新模型的微服务,对它你可以执行功能测试。那么现在你要负责保障的是,新的测试是否足够健壮的兼容老版本到新版本。

  2. 向后兼容:确保你的领域模型有能力处理向后兼容,那样老版本模型可以和新版本模型良好共存。

  3. 保持领域模型元素尽量原子化:越多的纠缠不清的聚合,实体,仓库,领域服务,独立的部署这些就会变成越复杂的问题。越小的、越原子的领域模型元素,才越容易处理变更。

拆分每个微服务一个领域模型

这是这个问题的一个有趣的解决办法,我见过一个案例是微软的容器化.Net参考架构[1]。每个领域模型被拆分成一个微服务,因此每个微服务本身就是限界上下文的子域。

一个自包含的微服务/限界上下文

现在你的微服务本身是一个自包含的限界上下文,它在一个更大的限界上下文中。它有自己的模型,仓库,应用服务,工厂等等。使用通信协议如REST和作事件发布/消费的服务总线,你现在有了一个自包含的单元,它可独立的部署。

缺点,当然是一个领域模型横穿多个微服务。一个权衡就是你必须更加如履薄冰的确保团队谨慎构建模型,使你不终结于偏离轨道的行为或者逻辑。虽然领域模型作为单包库可能是自包含微服务的痛点,但是可以确信的是,保持所有领域逻辑内聚在一个地方是优雅的。

有些要注意的是,在这个设计里你期望允许微服务有全栈的基础设施能力,比如访问数据库,访问服务总线,作为一个REST HTTP客户端等等。每个微服务的代码量都悄然变大了,最终这个模型里有了更多的重复代码。不是没法收拾(如果你很细心),但是仍然如梗在喉。

怎么做能缓解下呢:

  1. 保持领域模型元素尽量的原子化:想办法让每个微服务拥有领域模型中的一部分,会更简单些,但是你必须精心设计。不能让一个微服务做多于一件的事。这里的技巧就是保证这个“一件事”很小。如果你的微服务名叫“记账”或者“账户”服务,你就要麻烦了。那不是个微服务,那是个全流程的领域。要保持每个微服务是单一的任务。

  2. 用通信的工具(明智之举):你的订单服务需要从商品服务里获取商品详情吗?REST。你的快递服务需要知道什么时候订单准备好运输了吗?一个优雅的OrderReadyToShipEvent发出到事件总线,可以确保一个优雅的解耦。牢记重要的一点,两个微服务之间不应该是聊来聊去的。如果两个或者多个微服务总是你一言我一语的交互,你可能得反思一下设计了。可能一些从属于别的微服务的逻辑引起了频繁交互,或许你需要把这个不安分的行为重构出去。

  3. 保持松耦合的契约:我试图无论如何也不共享契约。我期望如果一个微服务兄弟服务(包括通过REST或者事件/命令方式的),它们中的每个都负责自己的副本模型版本控制。为什么?比如,如果我的订单服务有一个商品数据的接口契约,可以这样获取:https://ProductService/Products/{id},然后它会知道它会取回什么。如果商品服务更改了它的契约,只要新的契约没有打破旧的,那订单服务仍可以获取它期望的数据。订单服务可以晚些时候再去更新最新版的数据契约。另外,新增的商品信息的消费者可以享受到新版契约的好处,而不会影响我的订购服务。你甚至可以用内容协商[2]的思路来保证调用者得到它想要的数据版本。

总结

处理DDD和微服务时,不总是风和日丽的。确实还需要花时间去设计系统,基于实际需要,分析我们的应用的生命周期,以及什么选择对我们和我们的开发团队最好。

不同的方案都有它的优缺点,我不认为将DDD和微服务结合是万金油,还得具体问题具体分析,希望本文能对你有所启发。

相关链接:

  1. https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/

  2. https://en.wikipedia.org/wiki/Content_negotiation

原文链接:https://medium.com/it-dead-inside/domain-driven-design-in-the-era-of-microservices-2-implementing-your-domain-model-eafc2c04dead

为了方便大家交流,特意建了DDD交流群,扫描下方群二维码加好友,备注『DDD』,邀请您进群交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值