分层架构演化:从单体的插件化演化所引起的思考

开发 架构
最近,在为 Coco 优化分层架构之时,我陷入了各种决策困难之中。所以我通过不断地延迟决策,以摸清更适合现有系统的现状。换个简单来说,在危险边缘徘徊,以期待能获取最大的收益。

 [[387794]]

最近,在为 Coco 优化分层架构之时,我陷入了各种决策困难之中。所以我通过不断地延迟决策,以摸清更适合现有系统的现状。换个简单来说,在危险边缘徘徊,以期待能获取最大的收益。

在设计新的架构时,我们总会凭借原先的经验,并结合业务现状的需求,并根据未来的需求做出我们的设计。即:

  • 过去的经验。
  • 现在的需求。
  • 未来的方向。

种种因素的影响之下,它注定了我们无法设计一个满足所有历史时期的系统。未来会变成现在,现在会变成过去。

Coco 架构设计:从过去到过去的未来

原先对于 Coca 的各种设计问题,以及 Golang 对于多平台的支持问题等多方面的因素。迫使 Inherd 开源小组在 Coco 在初始阶段,便考虑了为 Coco 设计插件系统。直到最近,我们实现了插件系统之后,发现了原来设计的分层架构已经不满足现今的需求。

虽然,我已经知道新的分层架构应该如何设计,但是我并不想朝那个方向过去。我走走弯路,再看看是否存在一些更有意思的设计。

原始形态:单体架构

在设计初期,我在 Coco 中引入了类似于 Clean Architecture 的分层架构设计(不包含 Cargo 模块):

  • app,对应于用例(usecases)。
  • bin,对应于 controller。在 Rust 的构建系统中,bin 目录的会被构建出可执行文件
  • infrastructure,对应于 基础设施,如调用 Git 的接口、访问文件系统等。
  • domain,即业务实体、领域模式,包含了系统的业务设计。

在 domain 目录下,根据了我们的四大基本业务,进行了二次划分 :

  • cloc
  • git
  • framework
  • architecture
  • ……

尽管,我一直在说我采用的是类似于 Clean Architecture 的分层架构。但是实际上,并没有采用其中一些重要的设计,比如说通过依赖反转来控制流向的问题。从个人的角度来看:

  1. 它带来一定的架构复杂度,需要不断地传递相关的架构知识,能否在开源项目中推广,有待商榷。
  2. 后续可以通过重构来转换。我并非非常资深的架构专家,所以以学习为出发点更方便。

作为一个单体应用,这个分层结构凑合着:

  1. 不算太复杂,还能让开发人员知道哪的代码往哪里放。
  2. 可以按需演化为 Clean Architecture。
  3. 模块可以进一步按业务拆分。

故事的开始还是蛮美好的。

复用形态之模块化

为了在多个不同的系统/应用之间(即 Coco 项目的代码提供给其它应用)复用代码 ,系统中产出一些独立的模块,如 psa、framework 等等。这也是一个非常常见的模块化的场景。模块化在不同的语言里都有一定的相似之处。

譬如:在使用方式上存在本地使用和远程发布两种模式。在本地使用时,无需关注语义化版本等一系列的事项,只需关注于代码本身。一旦时机成熟,也就可以进化为可远程发布的模块。

从单体中出现模块化的一种典型形式便是,在代码库中以与源码同级的目录呈现。如下:

  1. ├── framework 
  2. ├── psa 
  3. ├── src 
  4. │   ├── app 
  5. │   ├── bin 
  6. │   ├── domain 
  7. │   ├── infrastructure 
  8. │   └── lib.rs 

这里的 framework 和 psa 便是独立的模块,一旦其与其它模块的依赖关系解耦开来,那么它就可以作为独立的应用发布。

复制 over 复用

顺便提一句,对于模块化的代码复用来说,如果代码量较少,那么可以尝试复制一份代码,而不是复用做代码。这样一来,我们可以通过此来解耦依赖。

插件化的架构演变

同时,为了灵活地扩展系统的功能,我们设计了插件系统。(事实上,更多地从意图上,我们只是为了减少包体积大小,这样可以方便地从 GitHub 下载)

于是乎,我们创建了独立的 plugins 目录,并在其中创建了对应的模块,如下的 coco_xxxx 即是插件。同时,我们使用了 plugin_manager 来作为插件的管理器(事实上,后面证明了,这个 manager 不应该独立作为一个模块存在):

  1. ├── framework 
  2. ├── plugin_manager 
  3. ├── plugins 
  4. │   ├── coco_container 
  5. │   ├── coco_pipeline 
  6. │   ├── coco_struct 
  7. │   └── coco_swagger 
  8. ├── psa 
  9. ├── src 
  10. │   ├── app 
  11. │   ├── bin 
  12. │   ├── domain 
  13. │   ├── infrastructure 
  14. │   └── lib.rs 

从设计和演进的角度来看,问题并不多,也可以使用。

演进:未来的未来

好了,由于经验上的不足,我们就面临了之前没考虑到的问题。

提取核心模型

从设计思路上来看,我们本应该在原先的架构模型中,提供一个 core 模块。而在这个 core 模块里呢,则用于提供一些核心的代码给插件和应用。

所以,很快地我们就创建了一个 core_model 出来了。我的本义也就只是提供一个核心模型。我不想像一些插件化项目中,在 core 中提供大量非核心的代码。

只是呢,随着第一个模型复用需求的出现,很快地就有了第二部分、第三部分。

再次抉择:基础设施层的改造

而插件之间除了模型的复用,还会有基础设施的复用。而这些代码,我又不想放到 core 里,所以就又需要抽取中一个 infra 的模块,用来共享基础设施的代码。那么问题来了,我们应该如何选择?

  1. 将原有的 infrastructure 提取到主目录下,作为单独的模块存在。
  2. 双层infrastructure,即只提取共用的代码,到主目录下,作为独立的模块。

从架构设计的思想来看,我是支持双层基础设施的存在。过多的无意识地复制这些公共代码,会导致这个包大小的进一步膨胀。一个典型的例子,就是我们在一个被称为 common 包的 jar 包里,看到一个 common 子包下,还有 common 目录的存在,即 xxx-common.common.common。

小结:架构的持续演化

故事就到这里了。哪怕一个再小的项目,它的架构模式也会随着系统的开发,不断地演化。如果不加以控制,那么系统可能会推动控制。而演进本身呢,也不会是一帆风顺的。

不过,我在思考一个新的东西,关于『分层架构适应度函数』。

Yiki:分层架构适应度函数

无论是在 Coco 还是在 Coca 里,我们都在尝试对系统的分层进行一个评级。而这个评级的其中一个依据是通过依赖关系,来确认各个模块之间的引用关系,从而判断系统的分层架构是否是符合需求的。

通过解析模块之间的引用关系,可以帮效地帮助我们厘清系统模块之间的合理度。

本文转载自微信公众号「phodal」,可以通过以下二维码关注。转载本文请联系phodal公众号。

 

责任编辑:武晓燕 来源: phodal
相关推荐

2022-12-15 17:15:42

数据库NoSQL

2016-04-21 10:10:31

Java应用架构

2023-07-10 18:38:53

2014-09-26 09:53:41

系统架构架构架构演变

2015-09-23 14:14:47

LinkedIn架构解析

2014-10-31 09:48:36

Go语言

2022-11-29 11:21:20

单体分层应用架构

2011-11-16 09:00:39

编程语言

2017-10-27 16:40:49

Web网站搭建架构演化图

2010-02-24 17:01:49

2023-11-01 11:38:44

嵌入式MVC

2012-11-20 10:04:46

Winform开发

2009-06-10 18:08:14

2017-11-09 15:38:26

OpenRTB 3.0演化

2009-07-22 14:53:45

ibmdwIT架构

2017-04-11 15:43:39

JavaScript模块演化

2015-06-24 15:30:46

2023-12-19 22:29:37

架构微服务系统

2015-10-22 10:35:06

2023-12-11 15:51:00

Python装饰器代码
点赞
收藏

51CTO技术栈公众号