引言
好久没有写自己的文章了,最近参与了公司的一个读书分享(Get Your Hands Dirty on Clean Architecture)发现了很多和本书作者共鸣的地方,也借着这个机会和大家总结下自己理解,如果没读过这本书的,也可以快速通过这篇文章帮你获取核心知识点。
这个文章主题是进程内架构落地实践,为什么要说落地,是因为后面会给大家看代码,毕竟理论相对抽象,很多文章你就算看完了实际上你还是不知道怎么做,所以阅读这个之前你可以放心最后会有完整的代码示例,你可以下来仔细去了解。
为什么需要了解进程内的架构设计
这个是一位技术人员,目前了他有一些空闲时间,他在review他们项目上的技术问题,看看是否能够解决之前遗留的技术债,要保证可维护性,减少开发成本,作为技术人员,肯定要追求卓越,美观的结构也是必不可少的,balabala...脑袋里面各种想法开始碰撞。
如果顺利的话,我们就可以开始优化之路了,但是现实往往很残酷,这个时候你会突然听到一个声音:等等!
他叫住你干嘛呢,我这边举例了三个比较常见的情景:
- 第一个就是你的合作伙伴找到了PM, PM突然找你,说我们之前提供的那个接口需要修改下,以适配他们的新的规范来,这个时候你很困惑,为啥呢,应为之前他们不是这么说的,并且规范之前也是对齐了的,现在又要改,不我不该,PM你去battle,好的情况是battle赢了,但是往往大部分的结果可能是:项目经理的实力不足以赢得与外部合作伙伴的战斗,或者外部合作伙伴发现 API 规范中的漏洞,并证明其正确性,还有最常见的一个理由外部合作伙伴还需要x个月来修复这个API,那最后都会还是需要我们自己去解决
- 第二个场景是PO或者项目经理,突然给大家说,这边来了一些新功能,并且要在这次的deadline一起上,你心里一咯噔,赶紧看一下时间,你发现还有3天就是deadine了,心里只能无数头caonima跑过去。
- 第三个场景是,你的客户突然找到你,他说有一个什么资料,可能是会议相关的,可能是报告,让你帮忙整理下然后发给他们,然而这个时候你已经很忙了,但是也不得不回复一个:好,内心默默流泪
那在这些场景的干扰下,你对于最开始做技术优化的想法很可能就会变成下面图片这个样子,有些小伙伴可能内心还是会过不去,甚至安慰自己,不是我不愿意追求技术卓越,而是没有条件
那怎么解决这个问题呢?首先我澄清一下,我们今天聊的解决方案是站在架构的视角,来帮助项目应对各种这样的外部变化,而减少对代码的腐蚀,避免总是采取捷径,捷径是什么意思呢,就是前面那个人的感叹,怎么简单怎么来,这种思路下往往是造成代码,架构快速腐化的元凶。那有没有一种架构能够像积木一样,具备很强的灵活性,适应性强,我们可以控制我们自己的立场,避免总是采取捷径的无赖呢?
---答案就是接下来会讲的这本书中的核心知识。
一些基本概念
在继续具体内容之前先对一些词做一个解释,防止小伙伴们产生歧义或者不太了解。
什么叫进程内架构
这个顾名思义就是在单个进程内运行的服务的架构设计,也就是实际运行起来作为最小个体的服务代码库的内部代码结构,它不是那种跨进程的多服务类别的架构设计,如果还是无法理解,我提一个东西你一定知道那就是三层架构(web,service,repository),以前我们写代码的小伙伴们应该都接触过,那你们所了解的三层架构就是这里聊到的进程内架构的一个实例,到后面还有什么很多其他模式,例如前端的MVVM,DDD的六边形架构等。
什么是领域中心的架构
说道领域模型,最近业内特别频繁的一个词就是DDD,各个大厂也都在落地自己的DDD,但是由于DDD的思想本身是抽象的,到后面才有了IDDD,它帮助指导了落地的具体步骤,但是实际上如果你有过落地DDD的经验,你就会发现不同的架构师或者技术负责人对于DDD具体落地的结构是有一套自己的认知的(大的方向可能一样,但是小的结构区别蛮大),这种问题就导致,不同项目上可能进程内架构风格五花八门,尽管都是DDD(这里有个前提是公司内部没有制定强制的统一的架构标准)。
那谈到了DDD往往就会聊到,整洁架构(clean architecture),又叫洋葱架构,这个核心思想也是领域为中心来设计的,那你可能会问整洁架构和DDD有什么区别呢?---答案是DDD是整洁架构的一个实例,也就是基于领域中心概念的一个更具体的架构模式。也是后面我们会谈到的六边形架构。
那我们知道DDD和整洁架构都是属于领域中心的架构模式,那么它的特点是什么?
如上图,就是环形,中间包裹的红色部分就是我们的领域逻辑,你可以看到无论是整洁架构还是六边形架构都是类似的环形,它的核心是体现出领域部分的业务价值,如果你的服务并不能很好的表现这个领域的业务价值,那么其实没必要纠结与是否采用这种架构模式,也许简单的分层架构更适合你。
书籍&作者介绍
我看的这个版本是在2019年的时候发布的,最新的书籍链接可以再作者Tom Hombergs的github账号下的这里看到。
作者是就职于ATLASSIAN公司的一名技术人员,这个公司大家应该都比较熟,例如JIRA, Trello, Bitbucket这些都是他们的产品。
从这里我个人感觉到为什么作者会机缘巧合的初版这本书籍,猜测是他们的很多产品对于长期的维护,以及随着时间推移,复杂业务的累计导致需要对服务架构内部的优化和重构会自然而然的增加,那么就势必就需要深入研究去解决,那有了一些成果和实践之后,发现外界有很多类似的诉求那么不如花时间把它整理成一本系统的指导书籍。
我之所以会这样猜测是因为我之前就面临过同样的问题,并且作者书本里提到的模块化的思路和我之前面临团队架构约束问题时的解决方案一模一样。
传统的分层架构为什么无法有效应对现在的问题
上面这个图就是我们作为开发人员经常会用到的架构模式---分层架构,常见的就是三层:Web, Business(Service),Persistence(Repository)。
为什么会为架构而烦恼
那这个时候我们再思考一下,我们为什么会为架构而烦恼呢? 前面的场景一开始我们就是想要优化,这里的优化其实就是进程内架构哈,那你为啥要优化呢,你不优化不就没有这些烦恼了么?佛系一点,天天开心多好,对吧。
那回答这个问题,其实我们需要了解一下架构的目标是什么,我们是为了什么目标而设计架构的?
这个问题其实有人会说,架构其实就是为了让软件能够工作,听起来确实好像如此,但是你反过来想一下,一个很差的架构做出来的软件就不能工作了么?你们之前遇到过的项目,大家觉得都是良好的架构么?我之前遇到一个维护了近20年的java项目,它也在好好工作中,虽然效率很慢,虽然添加新功能成本很高,虽然用户体验可能不好,但是客户仍然在使用它。
其实架构的目标其实是为了各种ility ,这个是指英语里面那些性质的后缀哈,例如readability,extendibility,maintainability等等,这里把这些特性换了一种说法结合书里的概念, 就是促进开发效率,促进快速发布,易于维护,让软件柔软(其实就是灵活),保持框架的边界,让方案具备多个选择可能性。
那这个时候,如果团队里面有一些非技术人员,特别是客户听到这些他们经常会问一个问题,那就是这些能带来什么价值?
这个其实也是软件架构的终极目标:
最小化软件生命周期的成本
这个其实就是通过良好的架构降低软件交付过程中各种的维护,开发等成本。结合现在大家常说的就是降本增效,这个对价值绝对是很高的。
分层架构哪里不对呢
那现在我们回过头来看一下,分层架构是哪里有问题呢?这里先做一个免责声明,分层架构他是一种可靠的架构模式 哈,不然也不会那么多人用,也用了很久了。 但是,如果没有额外的限制,它是一个很容易被腐蚀的架构模式。 为什么会怎么说?
数据库驱动设计
可以回忆一下之前的功能开发,你是在领域层(service层)开始开发的,还是先开始设计数据库表的?可能大部分的情景都是我们先一起讨论下表怎么设计,然后才开始制定业务层的结构,这种考虑是没问题的,因为这个架构结构下,自然而然的会把考虑点导向最下层,但是我们的业务是行为驱动的,数据库其实是状态的变化,我们把行为业务和数据状态强耦合在一起后,你会发现未来业务的一些变动会很容易影响数据结构,而改动数据结构又是比较麻烦的事情(连锁反应,历史数据等),所以这种情况下上层的业务代码的抽象和优化会变得越来越困难。
容易促成"难以测试"的捷径
这些捷径最终会导致代码难以测试,举个例子,我有一个功能,他只是一个简单的查询,我觉得我没有必要去打扰service层,按照传统的service你必须创建一个service出来,但是其实他没有任何业务,所以为什么不能直接跳过service调用持久层呢?一但这类的行为有了第一次,在团队内部就会开始变得普遍起来,那单测为什么困难呢,应为你需要mock的对象变多了,controller不仅需要mock service,还需要mock repo,并且业务逻辑可能也在controller分布,你的测试代码的维护成本组件升高。
这里看一个例子,这个例子不是前面讲的跨层依赖问题,是另一个点就是作者书里提到的broad service, 这个service是一个大而全的类,它里面的依赖特别多,那如果你是新来的开发, 你愿意给这样的类写单测么?
容易隐藏业务功能
其实这个点上面已经提到了一部分,就是分层架构下,我们的业务代码容易分散在各个层中,如果我们为了"简单"而跳过service, 他可能存在web层中,如果我们将某个组件往下放,放在持久层,然后调用,那么业务可能存在持久层,以便service和repo可以访问。 它会使得寻找合适的位置添加新功能变得很困难。
还有就是前面提到的分层架构不会对领域服务的"宽度"强加规则,随着时间推移就会形成上面看到那种broad service,那并行开发同一个功能的时候就会遇到很多冲突问题,并行可能就难以实现。
疑惑
这个时候你可能会有这个疑惑:既然传统分层架构有这么多问题,那为什么还被普片使用呢?
--- 这个前面声明的时候其实也提到了,就是分层架构是一种可靠的架构,他被大量的实践证明过。那为什么不尝试换一个模式呢?其实当一个人选择架构的时候,它会基于自己的经验和旁人的经验来综合判断,我们提到的整洁,六边形架构概念出来也很久了,但是其实你可以发现每个人落地的版本都不一样的,而且这里面有比较高的认知成本,所以为何不选一个理解简单,又被检验过的分层架构呢,虽然他会带来额外的维护成本,不过也还在承受范围之内
那要采取什么架构风格呢? 这个就是下一个核心知识了,前面引言也提到了。
怎样的架构可以帮助我们解决困境
答案其实大家可能都知道,就是采用环形架构的思想,这里隐射的就是整洁架构的思想。那我其实在了解这个的时候会有一个疑惑那就是这种结构为啥和分层结构不一样,它没有层么?
Dependency Inversion Principle
解答这个问题之前,我们需要先了解一下SOLID的概念,这个我相信开发小伙伴都知道,我们这里要理解为什么不是层结构,重点要理解这个依赖倒置原则
我们先看一下他的概念,原始概念其实就是:
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
- 抽象不应该依赖于具体实现细节,而具体实现细节应该依赖于抽象
对吧,这里做了一下提炼,他其实表达的就是:
我们可以选择任何代码依赖的方向,只要我们能够控制它
而在这里我们把依赖反转了,就是这个图里的例子,之前service依赖repo, 但是通过创建一个repo的接口,让repo蹭去实现它,最后变成了repo依赖service,但这个方向本身是由我们自己控制的,你可以反转也可以不反转,只要双方都是依赖抽象行为就可以
所以我们进一步提炼一下,在分层的时候存在上下层的关系,但是借助依赖倒置的原则,其实我们可以把层放平,就是这个样子,不存在上下的关系,都在一层,大家都依赖抽象即可,那这个其实还可以再抽象,就是我们可以把每个业务单元看成独立的组件,不同组件之间通过抽象接口依赖即可,就是最后这个图,他有点类似变成乐高积木了,我的架构其实没有层的概念了,就是不同的单元相互组合的结果,那这里的环形或者六边形,在我看来就是基于这种小的组件添加上特定的规则变成的,所以这种模式不是分层,这里如果是第一次理解,会有一点抽象,不着急可以下来自己想一想看看是不是这样。
那这样理解后,你会不会觉得打开了新大门,应为未来我们可以发明一种我们自己的架构模式对吧,但这个不是今天的重点,这里就不扩展了。
整洁架构
我们回到书里的内容,我们的解决方案是环形,其实就是整洁架构的思想(并且这种思想就是依赖反转的一种体现),那这里和大家快速讲一下整洁架构的概念。
首先这个架构概念很老了,他是在2012年提出的,有11年了,你可以看到这个架构的核心是图里红色的部分,也就是我们领域逻辑,我们这样设计的目的就是为了能够让业务聚合,且不受细节影响,这里的细节就是外部蓝色的部分,也就是我们的web, UI, DB这些,然后蓝色部分只可以访问绿色的部分,绿色的只能访问红色的,大概就是这样的一种模式。
Single Responsibility Principle
那了解完DIP,我们还有一个很重要的原则,同样也是这种架构解决方案里面主要体现的,就是SRP, 这个概念字面意思你可能认为就是一个模块应该只做一件事情,对吧,但其实不是的,它是指:
一个模块(或者其他的)应该只有一个理由被改变
怎么说呢,这里从微观和宏观两个角度,快速解释下。
微观
微观我们看一个具体的class,这里选择下图这个service, 这个service里面存在多个用于行为的职责,当多个业务规则变化时都会引起它的变化,这种设计就不符合SRP。
他的正向例子是什么呢,就是把用例拆分开,一个用户行为对应一个use case
这样拆开后有的小伙伴可能会立马说那不是会有很多的class,并且你怎么保证拆开后不存在冗余代码?首先这个问题确实是这样的,先不说类多少对系统存在多少影响,现在大多数架构都是采用微服务的模式,这样的微服务下你的class不太可能会膨胀到无法管理的地步。然后提到了冗余代码,这里我们看一下下面两个图先
对于第二个图,如果作为一个开发维护人员来说,你一定会很开心,因为你会很容易的找到你需要修改的地方,和你想要扩展的点,那么即使存在一定的冗余代码,对于项目维护成本来说这个是可完全接受的,并且也是推荐这样做的。
当然这种方式你可以在分层架构使用,但是他在六边形架构中更如鱼得水,应为我们的核心业务不会受到外部的干扰,例如数据库,web请求等。
宏观
现在站在宏观的角度,也就是架构的角度来看,对于传统的分层架构,其实持久层的变化会导致业务层的改变,这是不符合SRP的原则的
这里来看一下整洁架构,这里红色的部分其实是同外部的变化隔离了的,外部的细节变化不会直接影响业务代码,这种模式是符合SRP的概念的
DDD-六边形架构
说到DDD就首先要提一个他的前提,那就是:只有领域中心的架构才允许领域驱动设计
所以这里再次提醒如果你的服务只是简单的编排服务等(例如BFF, Gateway)或者简单的CRUD,那么DDD不要作为第一考虑。
这里先来看一下什么是六边形架构/端口&适配器
为什么是六边形,我不知道,我猜测是因为六边形比四边形好看,并且更能体现架构的复杂度。不过如果你觉得六边对你来说不合适,你可以选择适合你的任何形状
Talk is cheap. Show me the code
前面讲了那么多,我们还是来看一下具体的代码是怎么样的,首先这个代码不是作者官方的代码,是我个人基于我的一些理解结合自己的经验做了一些修改。
GitHub - Aron-X/domain-architecture-sample
由于篇幅问题,这里先不对代码细节做进一步讲解,会在后续文章单独进行讲解。这里你需要关注的几个代码要点可以提前了解下
总结
领域中心架构的价值是什么
下面这个图通过以往遇到的项目上的痛点,来反向体现领域中心架构的价值点。
领域中心架构的缺点是什么
什么东西都要分两面来看,它的落地也会遇到实际的问题,具体可能的问题如下:
第一个:因为我们有了比较清晰的架构边界,并且我们不期望一个边界的变化影响另一个边界内的改动,所以这里可能会存在不同边界之间的dto对象的转换
第二个:我们期望 use case尽可能的窄且表达一个用例,所以可能某些场景下会存在冗余或者重复的代码,但是这会带来更好的维护性
第三个:我们需要真正的业务逻辑,这种架构模式对于一个纯CRUD的简单服务来说是没有必要的,所以只有真正的业务逻辑才能体现他的核心价值
附加内容
新的问题
整洁或者六边形核心点都是为了内部的领域模型进行业务的聚合,也就是大家经常谈的建模,但是实际情况下真正需要充血的领域模型建模的业务场景有多少,大家遇到的项目你的领域模型有多少业务逻辑
另一个问题是领域建模的思想比较复杂,不容易被人掌握,实际运用的难度较大,那有没有一种简单的架构模式来替代这个呢
组件
要解决那些,本书作者最新给出的答案是组件化,一切皆组件,这个概念我在最开始接触依赖反转和六边形架构的时候就在思考这个,直到我们到这本书的作者最近的一次技术演讲他专门提了这个理念,发现这就是我想要的一种架构模式,但这个不是这次讲解的重点,大家感兴趣的话可以下来去了解)(最后变成想乐高一样的小块,每一个小块是一个组件,这样的话架构会变得特别简单,没有那么多的约束,只需要保证不同组件之间依赖抽象的行为即可。)
别忘了架构守护
这里提一嘴架构度量,想要做到架构守护,第一步是需要知道架构当前的状态,所以如果即使发现架构问题,可以参考一些下图的方式