如何保证同事的代码不会腐烂?一文带你了解 Alibaba COLA 架构

2023-11-17

本文开始前,问大家一个问题,你觉得一份业务代码,尤其是互联网业务代码,都有哪些特点?

我能想到的有这几点:

  • 互联网业务迭代快,工期紧,导致代码结构混乱,几乎没有代码注释和文档
  • 互联网人员变动频繁,很容易接手别人的老项目,新人根本没时间吃透代码结构,紧迫的工期又只能让屎山越堆越大。
  • 多人一起开发,每个人的编码习惯不同,工具类代码各用个的,业务命名也经常冲突,影响效率。
  • 大部分团队几乎没有时间做代码重构,任由代码腐烂。

每当我们新启动一个代码仓库,都是信心满满,结构整洁。但是时间越往后,代码就变得腐败不堪,技术债务越来越庞大。

这种情况有解决方案吗?也是有的:

  1. 小组内定期做代码重构,解决技术债务。
  2. 组内设计完善的应用架构,让代码的腐烂来得慢一些。(当然很难做到完全不腐烂)
  3. 设计尽量简单,让不同层级的开发都能快速看懂并上手开发,而不是在一堆复杂的没人看懂的代码上堆更多的屎山。

而COLA,我们今天的主角,就是为了提供一个可落地的业务代码结构规范,让你的代码腐烂的尽可能慢一些,让团队的开发效率尽可能快一些。

COLA是什么

COLA是由阿里大佬张建飞所提出的一种业务代码架构的最佳实践,并且已经在阿里云脚手架代码生成器中作为一个可选项,可见其已经拥有了一定影响力。

COLA 是 Clean Object-Oriented and Layered Architecture的缩写,代表“整洁面向对象分层架构”。

在COLA 4.0,也就是目前最新的版本中,作者将COLA拆分为COLA架构(Archetype)和COLA组件(Components)两个部分:

  • COLA架构:COLA应用的代码模板。
  • COLA组件:提供一些非常有用的通用组件,这些组件可以帮助我们提升研发效率。

两者互不干扰,可以独立使用。

COLA整体架构

首先主要谈谈COLA架构,COLA的官方博文中是这么介绍的:

在平时我们的业务开发中,大部分的系统都需要:

  • 接收request,响应response;
  • 做业务逻辑处理,像校验参数,状态流转,业务计算等等;
  • 和外部系统有联动,像数据库,微服务,搜索引擎等;

正是有这样的共性存在,才会有很多普适的架构思想出现,比如分层架构、六边形架构、洋葱圈架构、整洁架构(Clean Architecture)、DDD架构等等。

这些应用架构思想虽然很好,但我们很多同学还是“不讲Co德,明白了很多道理,可还是过不好这一生”。问题就在于缺乏实践和指导。COLA的意义就在于,他不仅是思想,还提供了可落地的实践。应该是为数不多的应用架构层面的开源软件。

COLA提供了一整套代码架构,拿来即用。 其中包含了很多架构设计思想,包括讨论度很高的领域驱动设计DDD等。

注意:每个人对于架构设计都有着自己的理解。所以对于COLA的架构,本篇文章也仅仅只是我自己对于COLA的粗浅理解,大家可以批判看待。

COLA分层架构

先来看两张官方介绍图

其次,还有一个官方的表格,介绍了COLA中每个层的命名和含义:

层次 包名 功能 必选
Adapter层 web 处理页面请求的Controller
Adapter层 wireless 处理无线端的适配
Adapter层 wap 处理wap端的适配
App层 executor 处理request,包括command和query
App层 consumer 处理外部message
App层 scheduler 处理定时任务
Domain层 model 领域模型
Domain层 ability 领域能力,包括DomainService
Domain层 gateway 领域网关,解耦利器
Infra层 gatewayimpl 网关实现
Infra层 mapper ibatis数据库映射
Infra层 config 配置信息
Client SDK api 服务对外透出的API
Client SDK dto 服务对外的DTO

这两张图和一个表格已经把整个COLA架构的绝大部分内容展现给了大家,但是一下子这么多信息量可能很难消化。

既然整个示例架构项目是一个Maven父子结构,那我们就从父模块一个个好好过一遍。

首先父模块的pom.xml包含了如下子模块:

<modules>
  <module>demo-web-client</module>
  <module>demo-web-adapter</module>
  <module>demo-web-app</module>
  <module>demo-web-domain</module>
  <module>demo-web-infrastructure</module>
  <module>start</module>
</modules>

start层

该模块作为整个应用的启动模块(通常是一个SpringBoot应用),只承担启动项目和全局相关配置项的存放职责。代码目录如下:

将启动独立出来,好处是清晰简洁,也能让新人一眼就看出如何运行项目,以及项目的一些基础依赖。

adapter层

接下来我们按照之前架构图从上到下的顺序,一个个看。

首先是demo-web-adapter模块,这名字是不是很新鲜?但其实,可以理解为平时我们用的controller层(对于Web应用来说),换汤不换药。

在COLA官方博客中,也能找到如下的描述:

Controller这个名字主要是来自于MVC,因为是MVC,所以自带了Web应用的烙印。然而,随着mobile的兴起,现在很少有应用仅仅只支持Web端,通常的标配是Web,Mobile,WAP三端都要支持。

cilent层

有了我们说的“controller”层,接下来有的小伙伴肯定就会想,是不是service层啦。

是,也不是。

传统的Web应用中,完全可以只有一个service层给controller层调用,但是作为一个业务应用,除非你真的只是个前端页面的无情吐数据机器,否则很大可能性你的应用会有很多其他上下游调用方,并且你需要提供接口给他们。

这时候你给他们的不应该是一个Web接口,应该是RPC调用的服务层接口,至于原因不是本文的重点,具体就不展开了。

所以在COLA中,你的adapter层,调用了client层,client层中就是你服务接口的定义。

从上图中可以看到,client包里有:

  • api文件夹:存放服务接口定义
  • dto文件夹:存放传输实体

注意,这里只是服务接口定义,而不是服务层的具体实现,所以在adapter层中,调用的其实是client层的接口:

@RestController
public class CustomerController {

    @Autowired
    private CustomerServiceI customerService;

    @GetMapping(value = "/customer")
    public MultiResponse<CustomerDTO> listCustomerByName(@RequestParam(required = false) String name){
        CustomerListByNameQry customerListByNameQry = new CustomerListByNameQry();
        customerListByNameQry.setName(name);
        return customerService.listByName(customerListByNameQry);
    }

}

而最终接口的具体实现逻辑放到了app层。

@Service
@CatchAndLog
public class CustomerServiceImpl implements CustomerServiceI {

    @Resource
    private CustomerListByNameQryExe customerListByNameQryExe;

    @Override
    public MultiResponse<CustomerDTO> listByName(CustomerListByNameQry customerListByNameQry) {
        return customerListByNameQryExe.execute(customerListByNameQry);
    }
}

app层

接着上面说的,我们的app模块作为服务的实现,存放了各个业务的实现类,并且严格按照业务分包,这里划重点,是先按照业务分包,再按照功能分包的,为何要这么做,文章后面还会多说两句,先看图:

customer和order分别对应了消费着和订单两个业务子领域。里面是COLA定义app层下面三种功能:

App层 executor 处理request,包括command和query
App层 consumer 处理外部message
App层 scheduler 处理定时任务

可以看到,消息队列的消费者和定时任务,这类平时我们业务开发经常会遇到的场景,也放在app层。

domain层

接下来便是domain,也就是领域层,先看一下领域层整体结构:

可以看到,首先是按照不同的领域(customer和order)分包,里面则是三种主要的文件类型:

  1. 领域实体:实体模型可以是充血模型(请自行了解),例如官方示例里的Customer.java如下:
@Data
@Entity
public class Customer{

    private String customerId;
    private String memberId;
    private String globalId;
    private long registeredCapital;
    private String companyName;
    private SourceType sourceType;
    private CompanyType companyType;

    public Customer() {
    }

    public boolean isBigCompany() {
        return registeredCapital > 10000000; //注册资金大于1000万的是大企业
    }

    public boolean isSME() {
        return registeredCapital > 10000 && registeredCapital < 1000000; //注册资金大于10万小于100万的为中小企业
    }

    public void checkConfilict(){
        //Per different biz, the check policy could be different, if so, use ExtensionPoint
        if("ConflictCompanyName".equals(this.companyName)){
            throw new BizException(this.companyName+" has already existed, you can not add it");
        }

    }
}
  1. 领域能力:domainservice文件夹下,是领域对外暴露的服务能力,如上图中的CreditChecker

  2. 领域网关:gateway文件夹下的接口定义,这里的接口你可以粗略的理解成一种SPI,也就是交给infrastructure层去实现的接口。

例如CustomerGateway里定义了接口getByById,要求infrastructure的实现类必须定义如何通过消费者Id获取消费者实体信息,而infrastructure层可以实现任何数据源逻辑,比如,从MySQL获取,从Redis获取,还是从外部API获取等等。

public interface CustomerGateway {
    public Customer getByById(String customerId);
}

在示例代码的CustomerGatewayImpl(位于infrastructure层)中,CustomerDO(数据库实体)经过MyBatis的查询,转换为了Customer领域实体,进行返回。完成了依赖倒置。

@Component
public class CustomerGatewayImpl implements CustomerGateway {
    @Autowired
    private CustomerMapper customerMapper;

    public Customer getByById(String customerId){
      CustomerDO customerDO = customerMapper.getById(customerId);
      //Convert to Customer
      return null;
    }
}

infrastructure层

最后是我们的infrastructure也就是基础设施层,这层有我们刚才提到的gatewayimpl网关实现,也有MyBatis的mapper等数据源的映射和config配置文件。

Infra层 gatewayimpl 网关实现
Infra层 mapper ibatis数据库映射
Infra层 config 配置信息

所有层讲完了,COLA4.0很简单明了,最后,在引用一段官方介绍博客原文来总结COLA的层级:

1)适配层(Adapter Layer):负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相当于MVC中的controller;

2)应用层(Application Layer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等。层次是开放的,应用层也可以绕过领域层,直接访问基础实施层;

3)领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次;

4)基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过gateway的转义处理,才能被上面的App层和Domain层使用。

COLA架构的特色

说完了分层架构,我们再来回顾下上面提到的COLA架构的几个特色的设计

领域与功能的分包策略

也就是下面这张图的意思,先按照领域分包,再按照功能分包,这样做的其中一点好处是能将腐烂控制在该业务域内。

比如消费者customer和订单order两个领域是两个后端开发并行开发,两个人对于dto,util这些文件夹的命名习惯都不同,那么只会腐烂在各自的业务包下面,而不会将dto,util,config等文件夹放在一起,极容易引发文件冲突。

前面的包定义,都是功能维度的定义。为了兼顾领域维度的内聚性,我们有必要对包结构进行一下微调,即顶层包结构应该是按照领域划分,让领域内聚。

业务域和外部依赖解耦

前面提到的domain和infrastructure层的依赖倒置,是一个非常有用的设计,进一步解耦了取数逻辑的实现。

例如下图中,你的领域实体是商品item,通过gateway接口,你的商品的数据源可以是数据库,也可以是外部的服务API。

如果是外部的商品服务,你经过API调用后,商品域吐出的是一个大而全的DTO(可能包含几十个字段),而在下单这个阶段,订单所需要的可能只是其中几个字段而已。你拿到了外部领域DTO,转为自己领域的Item,只留下标题价格库存等必要的数据字段。

COLA并不完美

诚然,COLA已经做的足够清晰简洁了,但是它仍然有不完美的地方,比如每个接口的出入参都会根据业务名做定义,导致了很多结构极为相似的DTO,DTO的爆炸增长是个问题。参考:ISSUE-271

但是总的来说,COLA只是给你提供了一种架构设计的思想,并不深入到强制你使用某种规范的层面,所以对于COLA中你觉得复杂,或者不理解的地方,很多时候需要你自己来做权衡,作取舍。取其精华,去其糟粕的运用到你的项目中。

总结

COLA架构并不复杂,COLA已经从1.0版本经过逐次精简,发展到了如今的形态。在阿里云代码脚手架生成器中作为一个可选项,足见其已经趋于成熟。

下一篇文章,我会和大家一起讨论下COLA组件库中的一些重要组件,比如扩展点组件(cola-component-extension-starter),状态机组件(cola-component-statemachine)等。这些组件并不强制和COLA绑定,你完全可以不使用这些组件,只使用COLA架构来设计你的应用。但是这些组件可以提升团队的研发效率。

我们下期再见,我是在阿里搬砖的工程师 蛮三刀酱

持续的更新原创优质文章,离不开你的点赞,转发和分享!

我的唯一技术公众号:后端技术漫谈

欢迎加我个人号:BrodyYang

参考

COLA Github

https://github.com/alibaba/COLA

COLA 4.0.0 版本

https://blog.csdn.net/significantfrank/article/details/110934799

COLA 3.1.0 版本

https://blog.csdn.net/significantfrank/article/details/109529311

COLA 3.0.0 版本

https://blog.csdn.net/significantfrank/article/details/106976804

COLA 2.0.0 版本

https://blog.csdn.net/significantfrank/article/details/100074716

COLA 1.0.0 版本

https://blog.csdn.net/significantfrank/article/details/85785565

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何保证同事的代码不会腐烂?一文带你了解 Alibaba COLA 架构 的相关文章

随机推荐

  • SHA-256算法实现过程

    整理一下SHA 256的实现步骤 1 定义8个32位常量 h0 0x6a09e667 h1 0xbb67ae85 h2 0x3c6ef372 h3 0xa54ff53a h4 0x510e527f h5 0x9b05688c h6 0x1f
  • 通过Java理解Kruskal算法

    今天 我将解析一段Java代码 该代码实现了Kruskal算法 用于在连通的无向图中找到最小生成树 首先 我们来了解一些关键组件 1 DisjointSet 不相交集 这是Kruskal算法中的辅助数据结构 用于管理不相交集的集合 Find
  • msvcr100.dll丢失的解决方法?三招解决msvcr100.dll丢失问题

    最近我遇到了一个电脑问题 就是在运行某个软件时提示缺少msvcr100 dll文件 起初我并不知道这个文件是什么 也不知道它的作用 但通过一番搜索和了解 我对这个问题有了更深的理解 并且也得到了解决的办法 解决方法一 确保你的两台电脑都是相
  • requests_模拟搜狗翻译

    01笔记 在搜狗翻译的url中 请求的方法是Post 所以我们需要通过requests post方法来请求数据 接着url的请求参数是一个字典 所以我们需要修改该字典参数的搜索关键词 且其他参数需复制请求 否则请求不到数据 最后该url返回
  • PyTorch 2.0来了!100%向后兼容,一行代码训练提速76%

    编辑 机器之心 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 全栈算法 技术交流群 PyTorch 官方 我们这次的新特性太好用了 所以就直接叫 2 0 了 前段时间 PyTorch 团队在官
  • Altium Designer-Net has no driving source警告消除的方法

    1 其实这个警告原因是 你图中有一个器件的管脚有属性 如I O 并且这个管脚设定了驱动源 你先从元件库中 找到这个管脚 把管脚的属性 改成下面图片 的这个样子 就好了 2 下面这种方法 只是快速 逃避警告 也是可以通过编译的 在进行原理图编
  • 爬取豆瓣电影数据并进行分析可视化

    学习爬虫爬取豆瓣电影数据并进行分析 整体流程如下 1 爬取豆瓣电影数据 2 读取豆瓣电影数据 3 统计各个电影的评论数 4 读取某个电影的全部评论内容 5 获取某个电影的关键词并生成词云图 6 对电影数据的关键词和评分进行辩证分析并生成热力
  • linux设备号

    什么是设备号 linux中设备号是用来标记一类设备以及区分这类设备中具体个体的一组号码 由主设备号和次设备号组成 主设备号用来标记设备的类型 次设备号用来区分在这类设备中具体的个体设备 为什么用设备号 我们知道 linux下一切皆文件 li
  • CentOS 8.5:mysql8 + php8 使用 phpmyadmin52

    使用 dnf 安装命令没有安装成功 下载安装 phpmyadmin 下载地址 最新版本为 5 2 phpMyAdmin Downloads etc nginx nginx conf 中的配置内容 server listen 80 liste
  • VS Code的神级插件Bito - GPT-4 和 ChatGPT 编写代码、解释代码、创建测试

    Bito是什么 Bito是一款插件 它目前支持VS Code Chrome插件 以及Jetbrains的全系列IDE 例如 IDEA PyCharm Clion等 可以说能够覆盖大部分开发同学了 Bito 通过将 GPT 4 和 ChatG
  • KMP算法理解

    学习了KMP算法 对此有了一些理解 通过博客分享 如有理解错误的地方 请纠正 文章目录 字符串的前缀后缀 最大公共长度数组获取 KMP算法 时间复杂度 字符串的前缀后缀 再说明KMP算法前见说下它用到的一些东西 给定一个字符串如 ABCDA
  • 1.机器学习的基础概念

    机器学习的基础概念 文章目录 机器学习的基础概念 机器学习的分类 一 监督学习 1 监督学习概念 2 监督学习流程 3 监督学习算法 二 无监督学习 1 无监督学习概念 2 无监督学习流程 3 无监督学习算法 总结 机器学习的分类 机器学习
  • python 博弈论 库_6个Python库解释机器学习模型并建立信任

    原标题 6个Python库解释机器学习模型并建立信任 在机器学习模型中建立信任的案例 全球道路上大约有12亿辆汽车 这是一个令人毛骨悚然的问题 您认为实际上有多少驾驶员了解车辆的内部运行情况 您可能已经猜到了 答案只是少数几个人 我们不需要
  • 拿金币 蓝桥杯

    问题描述 有一个N x N的方格 每一个格子都有一些金币 只要站在格子里就能拿到里面的金币 你站在最左上角的格子里 每次可以从一个格子走到它右边或下边的格子里 请问如何走才能拿到最多的金币 输入格式 第一行输入一个正整数n 以下n行描述该方
  • LINUX IO内存操作方法

    https wenku baidu com view f15358692bf90242a8956bec0975f46527d3a7f3 html
  • 快看!那个学vSLAM的上吊了! —— (一)综述

    不同于之前发布的文章 我将使用一种全新的方式 iPad Notability Blog的方式打开这个板块的大门 原因有两个 1 Notability更方便手写长公式 也方便手绘坐标系变换等等 2 之前Apple Pencil找不到了新破费买
  • Qt5.9中一个槽函数接收多个信号用法(函数sender())

    本文主要总结一种常用情况 当多个信号同时发送到一个槽函数时 需要在槽函数中判断 是哪个信号发射过来的 解决这个问题 主要用到函数 QObject sender 和QString sender objectName QObject sende
  • devops-部署web服务器环境

    1 java 环境 各 wenb 服务器准备 tomcat 运行环境 useradd www u 2000 mkdir apps cd apps tar xvfjdk 8u181 linux x64 tar gz ln sv apps jd
  • 手把手教你使用 Prometheus 监控 JVM

    概述 当你的 Java 业务容器化上 K8S 后 如果对其进行监控呢 Prometheus 社区开发了 JMX Exporter 来导出 JVM 的监控指标 以便使用 Prometheus 来采集监控数据 本文将介绍如何利用 Prometh
  • 如何保证同事的代码不会腐烂?一文带你了解 Alibaba COLA 架构

    本文开始前 问大家一个问题 你觉得一份业务代码 尤其是互联网业务代码 都有哪些特点 我能想到的有这几点 互联网业务迭代快 工期紧 导致代码结构混乱 几乎没有代码注释和文档 互联网人员变动频繁 很容易接手别人的老项目 新人根本没时间吃透代码结