代码工程结构最佳实践(面向微服务)
术语解释:
模块(Module):代码工程里面划分为多个小的、独立的子项目,子项目面向特定的功能场景,子项目之间可以相互关联。例如Maven工程(Project)可以被组织为多个模块(Module),此外每个模块都拥有自己的 pom.xml 文件
包(Package):包是一种组织类的方式,包的主要目的是防止命名冲突,并提供一种将相关的类、接口、枚举和注解等组织在一起的方式,从而简化代码的管理和使用。例如在Java中的Package
备注.本文也的相关代码示例都是基于Java的Maven工程
阿里服务分层规范
参考链接:
你的项目应该如何正确分层?
在阿里的编码规范中,其标准示例如下图:
图中分层介绍如下:
- 开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行网关安全控制、流量控制等。
- 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染、JS 渲染、JSP 渲染、移动端展示等。
- Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
- Service 层:相对具体的业务逻辑服务层。
- Manager 层:通用业务处理层,它有如下特征:一是对第三方平台封装的层,预处理返回结果及转化异常信息;二是对Service层通用能力的下沉,如缓存方案、中间件通用处理;三是与DAO层交互,对多个DAO的组合复用。
- DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 进行数据交互。
可以看到有明显的分层和复用的意义,具体实践为:
- service层的上层,作为第一层,对外提供业务交互接口,要求:轻业务逻辑、参数校验、异常兜底。
- service层作为业务组装层,包含业务编排逻辑,一般是针对特定业务定制化编写,复用性较低。
- Mannager层是基础逻辑层,遵循高内聚低耦合思想,粒度较小,提供给上层复用。
对于上述分层架构,需要考虑的另一个因素,是层次之间一定是相邻层互相依赖,数据的流转也只能在相邻的两层之间流转:
在阿里巴巴编码规约中,列举了下面几个领域模型规约:
DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
BO(Business Object):业务对象,由Service层输出的封装业务逻辑的对象。
AO(Application Object):应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。
此外还有:POJO 等
以上领域模型仅供参考,体现在代码工程上就是包(Package)名的定义。在实际工作中,也许只会约定其中几项例如:VO、BO、POJO 。
综上,阿里服务分层规范,在实际编码中对应的是包(Package)的规范,因此其对应的工程结构如下:
todo
COLA 4.0
参考链接:
COLA 4.0:应用架构的最佳实践
github.com/alibaba/COLA
COLA 是 Clean Object-Oriented and Layered Architecture的缩写,代表“整洁面向对象分层架构”。目前(截止到202402)COLA已经发展到COLA 4.0。
其的初衷旨在控制复杂度,救码农于水火,很大程度上是指导着程序员践行“高内聚,低耦合”。
其官方已提供了architect生成方案,并包含了配到的公共组件(cola-components目录下面),包括:
接下来回归到分成设计本身,COLA里面的很多设计思想都来自于DDD(DDD的思想是一定要去学习和贯彻的,特别是统一语言、边界上下文、防腐层的思想),其中就包括领域包的设计,其分层架构划分如下:
其代码工程结构(Module方面)划分如下:
其包结构(Package方面)命名建议如下:
最终通过其官方architect工具生成的示例工程如下:
COLA 是上述【阿里服务分层规范】的全面补全、改进。我们总结下其优点:
- 官方提供了基础的组件,文档齐全,开箱即用
- 分层设计做到了极致,模块间依赖关系清晰,有很好的指导意义
那么其有没有些不足,或者用力过猛的地方呢? 尤其是在应对编程工作时,COLA 能否满足所有人的诉求,例如:
- 开发效率方面:对于多个模块之间的依赖,需要都定义接口+实现类 —— COLA的分层设计是否太多了,是否可以下放部分到package
- 代码可读性:由于代码工程充满接口、实现类,而阅读代码时主要关注的是实现类(的实现逻辑)
- 工程结构方面:能否从复用、父级接口定义方面,再抽象出一个module(见下面【面向微服务的最佳实践】)
下面再浅谈一下,为什么我们会觉得“用力过猛”,大概是因为:
- 国内的软件开发氛围,是比较急躁的,讲究高效、快速实现,目标是尽快抢占市场(此处应该有狗头)
- 国内的程序员,受环境氛围影响,会追求快准狠的实现,都更喜欢简单、高效、有规律可循的设计
因此,该COLA架构是否一针见血的简洁呢?显然不是了。那么,COLA架构是面向什么场景的呢?答案就是:
多人协作、结对编程场景,即高级程序员负责定义上层设计(主线逻辑、接口),中初级程序员负责补齐细节逻辑(实现类,以及domain层)。听起来有点欧美风,早上写代码下午喝咖啡的味道。
面向微服务的最佳实践(基于COLA4.0调整)
我们通过极限思维去发散思考,也许工作中会有些稍微极端的、常见的挑战,例如:
- 单人开发、跟进 十几个系统,或者是十万行以上的代码
- 因人员变动,B员工要尽快接手A员工的 十几个系统,或者是十万行以上的代码
- 排查事故时,检查相关代码时,需要往下翻查大量的接口+实现类(此时接口的存在是多余的)
- 外部系统希望提供DAO层的pom依赖
- 外部系统希望有更纯粹的API、DTO(而不是开箱即用的client、facade)
因此,我们期望能够更合理地分层,包括:
- 减少非必要的模块,以整合过多冗余的接口-实现类(减少接口的定义),对独立开发者友好,对小众开发者友好(一般来说,垂直领域的微服务是由一两个人开发、维护)
- 更科学的依赖关系,模块间带有分层、继承的理念
具体的模块划分、之间的依赖说明如下:
对应模块说明如下:
公共组件:由架构组开发的后端工程基础组件
—— 封装parent包,以初次声明properties(包含project.build.sourceEncoding、project.reporting.outputEncoding、java.version、依赖包的version等)、声明dependencyManagement等
—— 封装common-rpc包,提供微服务注册、发现、调用以及相关治理能力
—— 封装common-dto、exception包,以声明公共模型类
—— 封装功能性组件,如定时任务、MQ等
demo-api:声明对外服务的能力,业务系统的父级
demo-facade:继承demo-api,补齐rpc(或者直接调用)的能力,向外部应用提供开箱即用的能力(调用门面)。这里命名没有用client,主要因为client概念过大、容易产生歧义。
demo-app:继承demo-api,是业务系统的主逻辑,也是发布包
demo-dao:持久层相关,一般结合mybatis实现,带generator能力
结合包名的整体设计如下:
示例工程(下载地址)如下:
腾讯FIT后台系统工程结构
工程结构如下:
Q & A
代码工程为什么拆分出dao模块?
答:确实有不少的模板工程不会独立拆分dao。那么我们思考下拆分的好处:
1.dao模块专注持久层,对本系统而言会更清爽些,还可以提供给其他系统依赖(例如数据仓库、数据湖的建设落地)
2.dao模块可以更好地做代码generate的事情,可以整合mybatis相关能力,如generator组件。此时即便工程其他模块有代码错误,只要dao模块正常则能在idea上执行代码generate。
为什么要封装公共组件,能用Spring Cloud Alibaba代替吗?
答:Spring Cloud Alibaba开箱即用,可以用于快速搭建业务系统。然而,Spring Cloud Alibaba是以依赖方式嵌入,而不是作为parent角色,因此Spring Cloud Alibaba对工程没有约束力,仅有功能上的整合。
为什么要封装公共组件的parent包?
答:parent包作为业务系统的父级,可以提前声明properties、dependencies、dependencyManagement,可以有效减少业务系统的声明内容,帮助业务系统更专注于业务开发(使业务代码占95%以上),可以帮助业务系统减少依赖的修改、简化依赖包。此外,好的架构团队能够让parent更科学、更强大。
公共组件需要建设到什么程度?
答:对于devops流程,能够帮助相关开发者提高效率,就是有意义的建设。
假如公共组件做得足够好,则能够很大程度做到开箱即用;同时相关代码也能形成内部闭环,最终业务代码逃离公司环境也是难以执行(保密要求)