如果花时间了解一下 DDD 的历史,就会发现 DDD 已经存在了很长时间,单单按照 Eric Evans 成书那一年算起也已经有超过 10 年的历史了。但即使在刚开始的那几年,DDD 也只能说是不温不火,只是小圈子里人们的谈资,鲜少看到分享的文章(至少国内给我的感觉如此)。有意思的是大约三年前开始,DDD 重新回归大众的视野,无论是线上的文章也好,还是线下的各个大会,DDD 成为当仁不让的主角之一。
这背后的原因错综复杂,很难找出单一的解释。不过细心的阅读近几年来的 DDD 相关文章,你会发现有个名词以很高的频率与 DDD 一同出现,它就是「微服务」。对于微服务的分歧其实很大,或褒或贬,双方往往走向两个极端。推崇微服务的人觉得这是解决大型系统混乱,耦合的一剂良药,而反对的人觉得这只是架构师没事找事,平添了许多复杂性却没有带来有价值的收益。
其实很多新技术,新事物都是如此。诞生之初被寄予厚望,看上去有一统宇内八荒的气势。但是随着问题的出现,就招来了更大的质疑,反而落入了低谷。直到随着时间的流逝,那些真正的实践者们筚路蓝缕,脚踏实地的解决问题,让这些技术重新回到大众的视野中。虽然最近一年多谈论微服务的少了,但是我看到的是更多微服务落地的项目,虽然并不是每个都非常完美,但是绝大部分都达到了预期目标(所以最近搞「中台」的同学们不必灰心,多做一些落地具体的方案才是回应质疑最好的方式)。
之所以 DDD 与微服务常常同时出现,原因在于 DDD 中的很多实践能够帮助微服务更好的落地,完成的质量更高。不仅是对于单个的服务,对于整个微服务的架构,DDD 都能够起到很好的指导。而例如事件驱动,CQRS 这样源自 DDD 的模式,在微服务的上下文中显得更为合理,也能够真正的发挥自身的能力。
之前也有同学希望我写一些有关微服务的文章,所以我也恰好借着 DDD 的机会分享一些 DDD 与微服务结合的心得与经验,希望能够帮到有需要的人。这是系列的第一篇,我会把重点放在微服务上,而下一篇则会分享如何结合 DDD 做好微服务的拆分。在开始之前还是需要提醒一句,在软件架构方面并没有所谓的银弹,适合我的解决方案并不一定适合你,而你现在用的挺好的技术在未来可能成为最大的瓶颈。所以在自己的实践中,要学会独立思考,不要选最流行的,而是要选最适合自己,能为自己创造最大价值的方案。毕竟就像李文秀在书中末尾处所说的,「别人硬要给你的,就算好得不得了,我不喜欢,终究是不喜欢。」。
为什么选择微服务?
单体架构的局限
微服务是一种「架构风格」,与之对应的是「单体架构」。在讨论微服务的优点之前,不妨先看看单体架构有哪些优缺点,单单知己是不够的,知彼也很重要。每个开发者对单体架构都很熟悉,是我们日常开发过程中最普通,最常见的一种架构风格。说白了就是把相关功能都放在一个 codebase 下,编译,测试,发布都是以一个整体进行的。最大的优点就是简单,易操作。开发简单,大家代码库一拉,就能在上面开发新功能了。端对端的测试也简单,调用一个接口,看看数据库数据是否符合预期即可。而发布部署就更简单了,如果是 Java 项目,一个 war 包或是 jar 扔到服务器上就行了。
但是随着项目业务的发展,规模的变大,单体架构的缺点就凸显出来了。想象一下一个项目中存在着上万个源文件,后台数据库中有上千的表,单单编译一次可能就需要十几分钟的时间,跑一次完整的单元测试那更是万万等不起,这样还能搞敏捷,搞 DevOps 吗?诚然单体架构的系统同样通过 DDD 这样的架构模式提升可维护性,但这也是有上限的,随着代码的越来越多,业务逻辑的越来越复杂,项目的墒只会增加,最终难逃架构腐化,系统模块间紧密耦合,开发人员不敢动遗留代码的结局。
解决软件复杂度的方法有很多,其中我们最熟悉也是最熟练的就是「分治」。我们将可以复用的代码放入不同的函数中,将一系列有关的行为与数据放入类中,这是代码层面的分治。我们将多个相关功能的类放入到模块中,形成了多个不同的编程模块则是更高一层,在模块层面的分治。对于单体架构面临的问题,同样可以通过分治来解决,即把拆分的层次再向上移动一层,将一个大型的系统拆分成多个「服务」,而这也就是「微服务」架构了。
微服务的优势
在谈及微服务时,我们需要明确,我们如何定义微服务呢?比较常见的误区是把注意力集中在了「微」字上,例如曾经看到文章说需要把每个服务的代码限制在几百行以下,源文件不得超过多少个才算微服务。我自己觉得这有照本宣科的意思,其实微服务的特点在于「松耦合」与「完整的服务」这两项上。
我们先看「松耦合」,这意味着服务应该是独立的,可以独立开发,独立测试,独立部署,不依赖于任何的外部系统(基础设施除外)。当其他系统发生问题时,当前的服务依然可以向外部提供部分服务,而不是向单体应用那样,如果当前系统存在类似内存泄漏这样的问题,会导致整个系统的响应时间变慢,将问题蔓延至其他模块。
同时松耦合也使得快速迭代,上线成为可能,不需要在等待其他模块开发完成,就能够独立的进入开发,生产环境。而这也会影响到你的版本管理的分治策略,你可以很方便的使用基于主干的分支管理策略,不需要担心长期存在的 feature 合并时发生令人头疼的冲突问题。
其二是「完整的服务」。从业务功能的角度出发,每个微服务都应该提供一组独立,完备的业务服务。这也是很多时候让架构师难以把握的方面,怎么样才是独立,完备,书上是找不到答案的,更多的时候这更像是一门艺术而不是技术。但是也不用过于担心,DDD 恰好能够通过限界上下文,核心域这样的概念帮助我们不断的完善服务划分。这也会在之后的服务拆分的篇章中详细讲解。
微服务是万能的吗?
答案很明显,微服务不是万能的,甚至在有些场景下会放大原有团队的缺点和问题,导致系统的稳定性变的更差。天底下没有免费的午餐,在引入松耦合这些优点之后,微服务带来的是更复杂的系统架构,对于团队,基础设施,运维更高的技术要求,如果整个团队或是组织无法满足这些要求,无法妥善的处理微服务带来的副作用,那么做好的选择就是先提升内部能力再考虑架构的升级。
一个比较常见的场景是分布式事务的问题。单体架构一般都使用关系型数据库作为数据存储机制,通过关系型数据库的事务机制来保证数据的一致性。但是在引入微服务之后,对应的数据库也会进行拆分,甚至使用其他类型,例如 NoSQL 这样的数据存储,因此并没有办法保证严格的数据一致性,需要引入 BASE 这样的柔性事务,达到数据的最终一致性。如果一个团队没有在前期最好足够的调研,并采取逐步替换策略,那么很可能在架构迁移的第一阶段就遇到巨大的障碍。
另一个常见的问题则是对于基础设施和运维的挑战。在原本单体架构的场景中,运维和发布的步骤很简单,通过一些简单的脚本就能完成自动化的发布。但是在微服务架构中你需要面对的可能是上百个容器的监控,几十个服务之间调用链的分析和追踪,当出现问题时需要在极短的时间内定位到问题。如果在这方面缺乏积累,那么就会出现线上有问题都不知道是那个服务引起的尴尬场面。
小结
说了这么多,关键点在于不要盲目的迷信某种技术架构,选择合适自己的最重要。如果选择了微服务,那么开始之前先了解微服务架构有哪些必要的先觉条件,评估自己是否满足这些条件之后再考虑下一步的计划。最后 DDD 中的很多模式能够帮助你理清微服务中的许多问题,提供更好的解决思路,这也是我之后文章中会详细谈及的,希望感兴趣的不会错过吧。
作者:JoshuaJin
链接:https://juejin.cn/post/6844904162480619528