—— 上个文章,笔者对library,framework以及toolkits三者的异同做了一些分析,这篇就接着上篇文章来继续聊一下,知道这些的区别后,对我们架构或设计系统时会有什么帮助? 我们都知道,library,framework以及toolkits三者,对于我们的系统来说,都属于**第三方依赖**,所以,本篇笔者就我们的架构中要如何正确应用与处理第三方依赖说一点自己思考
上个文章,笔者对library,framework以及toolkits三者的异同做了一些分析,这篇就接着上篇文章来继续聊一下,知道这些的区别后,对我们架构或设计系统时会有什么帮助?
我们都知道,library,framework以及toolkits三者,对于我们的系统来说,都属于第三方依赖,所以,本篇笔者就我们的架构中要如何正确应用与处理第三方依赖说一点自己思考
1 架构之困–大泥团架构
事实上,任何一个项目或产品,系统都不可能脱离第三方依赖而存在,在软件行业的今天,很难想像有不依赖第三方的架构存在。一个架构中或多或少都需要依赖第三方依赖。
可惜,很多架构在这方面处理的不是非常妥当 ,对于任何第三方依赖不加处理,不加识别的添加进架构中,造成耦合,随着业系统的发展壮大,各种依赖及业务之间互相耦合,整个系统慢慢的演变为蜘蛛网式的依赖关系,最终不可避免的出现大泥团架构。
并不是说一个架构能完全做到不耦合第三方依赖,这种想法也是空中阁楼,不切实际的,架构或多或少会与一些第三方依赖耦合,这种我们称之为适当的耦合
所以,笔者认为,一个合格的架构师,至少要能正确的识别与处理架构中的第三方依赖。这也是我们能避免架构走向大泥团架构的基础
2 对第三方依赖进行分类
在要学习如何正确识别与处理架构中的第三方依赖之前,笔者认为,要对架构中的第三方依赖做适当的分类,不可能所有依赖都是平等或重要性一致的。
笔者基于自己的考量,对依赖做了一个划分,将第三方依赖分类为:元依赖,依赖倒转,替换式依赖,耦合式依赖几种

2.1 元依赖
定义
元依赖是指你的架构中核心业务代码不可能解耦的依赖
如笔者前述所言,没有任何一个架构能建立在不需要第三方依赖的基础之上,再简单的架构,再单一的业务,也一定会基于某种特定的语言及一些基础库而构建。
明白你的架构的元依赖是什么是非常重要的一件事,记住元依赖是你核心业务代码需要依赖的部分,所以这一部分需要尽可能少。
在Robert.C Martin的The Clean Architecture中,元依赖就是Entities需要的依赖。而在领域驱动思想的模式中,元依赖就是你的领域层需要依赖的部分

如果你是基于Java来构建一个架构,那么以下的部分你可以纳入你的元依赖
- Java语言核心
- 领域抽象或依赖
- 主流认可的的一些基础工具库,如Apache Common io
再如笔者的设计的基于Kotlin与Vert.x的响应式领域驱动框架,笔者设计的元依赖包括:
- Kotlin语言核心
- Vert.x Core
- kotlin-coroutines协程
其中,Vert.x Core是实现响应式架构不可能缺少的,而kotlin-coroutines实现同步式风格异步编程不可或缺的依赖,因此它们都被设计成元依赖
如下示例,suspend以及Future这些关键字表明它是一个同步式异步编程风格的响应式模型
suspend fun createISVClient():Future<ISVClient>{
return repository.save(this)
}
如上代码所示,在核心业务中,代码只依赖了元依赖
所以,元依赖是保证你核心业务整洁的关键。
很难想象,如果核心业务耦合了各种各样的框架,比如数据库操作,缓存,日志,校验,数据转换等,这种架构后续如何能避免走向大泥团的困境呢?
记住,元依赖应该只包含极少必不可少的依赖
2.2 依赖倒转
当然,如果照笔者上述所言,只依赖这么少数的几个玩意,是不可能编码出任何能实际运行的系统的。一个系统必然要涉及到存储,风格,日志,REST API或其它等各种各样的必不可少的依赖。
- 我的数据如何存储?
- 是否需要缓存以加快性能?
- 如何对外公开API,HTTP还是SOCKET?
- 如何做授权?
如笔者上述随意列的这些点,都是架构中需要考量,而且任何一个点,我们都会选用成熟的生态来帮助我们,也就是笔者在上篇文章中所说的library,framework或toolkits。
那我们要如何处理这样的依赖呢?
那隔离式依赖就是处理此类依赖
定义
依赖倒转是指架构中实现特定功能特性的依赖,我们会改变对其的依赖关系,将架构依赖它们转变为它们是对架构需求的实现
我们就拿数据存储这个来举例说明
假设我们使用MYSQL数据库,然后再使用JPA,这是一种很流行的架构技术选择,也是大家比较熟悉的。
大泥团架构中的实现风格
suspend fun createISVClient():Future<ISVClient>{
al promise = PromiseImpl<T>()
exists(ISVClient::class.java,this.id).onSuccess { exists ->
if(exists) {
sessionFactory.withTransaction { session, _ ->
session.merge(this)
}.subscribe().with({
promise.onSuccess(it)
},{
promise.fail(it)
})
}else{
sessionFactory.withTransaction { session, _ ->
session.persist(this)
}.subscribe().with({
promise.onSuccess(this)
}, {
promise.fail(it)
})
}
}
return promise.future()
}
如上述代码所示,因为我们在架构中使用了JPA, 于是大多数大泥团架构的做法就是在业务核心层中不加识别的直接使用这些框架的API。
随着类似的使用越来越多,最终架构依赖的越来越多,并且相互交织影响,这就是大泥团架构的基础与来源
如果我们使用倒转式依赖,那么风格则会是
// 我们在核心业务中定义了一个存储相关的仓储接口
interface OAuth2ClientRepository: EntityRepository {
suspend fun queryClientByClientId(clientId:String): Future<OAuth2Client?>
}
// 我们在核心业务中只依赖接口,并未依赖JPA
suspend fun queryClientByClientId(clientId:String):Future<OAuth2Client?> {
return repository.queryClientByClientId(clientId)
}
我们将JPA的实现最终通过依赖倒转的方式,将其变成一个实现,而不是依赖 (这个是另外一层,不在核心业务层)
class OAuth2ClientRepositoryHibernate :EntityRepositoryHibernate(), OAuth2ClientRepository{
override suspend fun queryClientByClientId(clientId: String): Future<OAuth2Client?> {
return singleQuery(OAuth2Client::class.java,"from OAuth2Client where clientId = :clientId", mapOf("clientId" to clientId))
}
}
与上述实现类似,你在处理其它诸如缓存,日志,数据处理,监控或其它等,都可以使用将其设计为倒转式依赖
所以,在面向对象的架构与设计中,依赖倒转是一个非常重要的概念,它也是实现倒转式依赖的基础。
2.3 替换性依赖
事实上,仅包含元依赖与倒转式依赖已经可以处理大部分情况了。但是做为一个架构师,你还必须知道替换性依赖。
定义
替换性依赖是指基于依赖倒转的基础之上,提供多种可替换实现的依赖
也就是你的架构中部分功能,在依赖倒转的基础之上,提供同一种功能特性的不同的可替换性的实现。
场景说明
比如你的架构中有设计媒体中心这个功能特性,它提供两个业务场景
- 媒体上传
- 媒体下载
媒体的上传下载可选的技术方案挺多,比如基于本地文件系统,基于fastdfs或基于阿里云OSS等,你可以把这些做成替换性依赖。
这有什么作用?
当然会有用,不同技术方案的优缺点各不相同,在不同的场景下使用不同的替换性方案是更佳的做法,比如在演示的场景下,你使用本地文件这种实现就行,部署起来最简单,又不影响你演示的效果。
做为架构师,你需要为你的架构考量上述场景,一些功能特性不可避免的要做成替换性依赖
2.4 耦合式依赖
当然,不是所有的依赖都得这样,要么元依赖,要么使用依赖倒转来隔离。
在一个架构中或多或少有些东西我们不想隔离,因为这有时候显得有一点多余,也存在一些依赖,压根没法隔离,比如Spring MVC这样的框架,很难想像如何隔离它。
对于不想隔离的,比如OAuth2授权实现,我们选择了Spring Security来实现。
那我们是不是有必要,自己抽象整理出一个授权或权限模型与接口,把Spring Security变成一个依赖倒转的实现?
这个问题,笔者认为要根据不同条件来考量,当然能做成依赖倒转在架构上是更妥当的做法,但如果每个项目或每个功能特性点都这样设计,有点矫枉过正。有时候并不需要做到这个地步。
所以,这类情况下,笔者认为你只需要把它设计为耦合式依赖就可以了,但是一定限定其不能侵入或耦合到核心业务,如领域驱动中的领域层
而对于一些压根难以解耦的框架,如Spring MVC,对它的耦合也是必不可少的
3 library,framework,toolkits与依赖
事实上,第三方依赖就是library,framework,toolkits它们其中的一种,当我们能理解或意识到第三方依赖可以根据不同的需求分为上述不同的类别之后,我们才能谈得上更好的设计出一个更具可维护性的架构。
如笔者在上篇文章中所分析的library,framework,toolkits三者的异同的基础之上,再结合笔者今天说的第三方依赖的分类,那我们如何把它们结合起来考量?
在选用library或framework时,我们如何考量它们,把它们设计成何种依赖? 这一点笔者下个文章再谈。