前言
最近学校课程要求做一个简单的微服务系统,正好借此机会拆解一下微服务系统的架构设计。
系统要求如下:
该基础业务平台应包含以下功能:
- 基于RBAC的系统权限管理功能,支持用户、角色和权限的可配置功能。在此基础上,实现基本的用户登录、注册等用户管理方面的功能。
- 提供统一的系统安全、缓存、日志、异常处理等功能接口。
- 提供统一的后台管理功能,可以对系统中的数据表进行后台管理。
- 在基础框架上,设计一个简单的业务场景(应至少涉及两个不同的角色、两个不同的业务部门),实现基本的业务功能。
平台的非功能方面的要求:
- 采用微服务架构:提供统一的服务网关、服务注册发现和服务间通信接口以及其他的服务管理方案。
- 采用前后端完全分离的架构。
- 兼容性要求:支持主流的浏览器,包括Chrome、Edge、Firefox和Safari。
- 安全性要求:系统达到等级保护二级要求,能够通过相应的安全性测试。
- 性能要求:系统应支持100并发,给出相应的测试结果。
单体架构
我们都知道单体项目的主流架构是分层架构,大概分为controller、service、dao、entity等层级,每个板块下可能会有多个业务类。但是单体项目中一个项目只有一个模块,所有的"包"都直接放在这个大模块中即可。
- Controller层:
- 作用: 负责接收用户的请求,调用相应的业务逻辑处理,并返回结果给用户。
- 实现: 包含处理HTTP请求的逻辑,解析请求参数,调用Service层的业务逻辑,最后将结果返回给用户。
- Service层:
- 作用: 执行业务逻辑,处理业务规则和流程,对数据进行处理和组织。
- 实现: 包含具体的业务逻辑,调用Dao层进行数据的读写,确保业务的正确执行。
- Dao(Data Access Object)层:
- 作用: 负责与数据库进行交互,执行数据的持久化操作。
- 实现: 包含数据库操作的具体实现,例如SQL语句的增删改查等。
- Entity层:
- 作用: 表示业务领域中的实体,通常与数据库中的表结构相关。
- 实现: 包含数据的定义,通常是一个与数据库表结构对应的Java类,类中的属性对应表中的字段。
首先我们来看看该项目的单体架构图,每层中的user、auth、business1、business2都是业务类

分模块+分层架构
然而该项目要求用微服务架构,微服务项目中通常需要按业务拆分出多个模块,那么项目架构是怎样的呢?
所以首先我们需要将该项目按业务分模块,那么就变为了分模块+分层架构。在微服务中还有两个要求:
- gateway作为服务网关统一管理请求
- 每个服务需要能调用其他服务模块,所以每个服务模块中还需要加上client这一层
- 每个服务需要能提供服务模块给其他模块调用,所以每个服务模块中还需要加上api这一层
api层、client层、controller层各代表什么意思?
- **api层:**提供给内部服务调用的一系列接口,属于被调用者
- **client层:**主要负责发送HTTP请求需要调用的模块,可以理解为一个发送HTTP请求的封装层,属于调用者
- **controller层:**提供给外部服务调用的一系 列接口,属于被调用者
api层与controller层的异同
- **相同点:**都是提供HTTP接口给外部服务调用
- **不同点:**controller层提供给外部服务(例如前端调用),而api层提供给内部服务调用,比如架构图中的其他服务
关系还是有点乱的,直接一图解千愁吧!

那么据此得来的架构图如下

这样是否就可行了呢?能用但问题大了,强烈不推荐:
极度高耦合:(1)A、B服务调用C服务时都需要写相同的client层(假设叫Cclient类),那么Cclient类需要复制两份分别在A、B服务中。(2)且Cclient类的方法返回值一定是C服务中的实体类(例如C服务是User服务那么可能是UserDO的数据库实体类),那么还需要将涉及到的所有实体类一并复制两份给A、B服务。
好家伙,一旦Capi相关的实体类有任何改动,那么Capi需要更改,调用C服务的Cclient代码中相关的实体类自然也全都需要修改,简直是灾难啊!
如何解决?
-
首先解决Cclient类需要复制两份的问题
一个经典解法就是抽取模块,功能复用。将client层的代码给抽取出来写一个统一的HTTP请求服务工具,那么client层的代码就只需要引入工具调用即可,实现功能复用。不过还好,这个问题
@FeignClient
注解帮我们完成了,详情看这篇文章[《Java远程调用神器:@FeignClient揭秘,轻松搞定微服务通信!》](Java远程调用神器:@FeignClient揭秘,轻松搞定微服务通信! - 掘金 (juejin.cn)) -
然后解决实体类需要复制两份的问题
其实从这篇文章[《Java远程调用神器:@FeignClient揭秘,轻松搞定微服务通信!》](Java远程调用神器:@FeignClient揭秘,轻松搞定微服务通信! - 掘金 (juejin.cn))中已经可以看出解决方法了,聪明如你,应该发现了client和api两个类几乎代码都一样。所以我们就可以想方法复用,将client继承api,然后client加上了
@FeignClient
注解即可,这就引出了微服务架构了
微服务架构
经过上面的分析,我们将上面提到的api以及相应的实体类抽取出来形成单独的模块,作为interface模块,那么另一个对应的就是service模块,即每一个业务对应的服务模块再分成interface和service模块,架构图及依赖关系如下

依赖关系使用Maven即可实现
xml
<!-- cloud-user-service的pom依赖 -->
<dependencies>
<dependency>
<groupId>com.cloud.auth</groupId>
<artifactId>cloud-auth-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
......
<dependency>
<groupId>com.cloud.common</groupId>
<artifactId>cloud-common-web</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
结束了吗?到这里系统基本可用,没有大问题,但还是不太推荐,因为实体类全部放到interface模块中不太优雅。
在《阿里巴巴Java开发手册》中写到,实体类还分为DO、DTO、VO等等类型。
-
**DO:**与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
-
**DTO:**数据传输对象,Service 或 Manager 向外传输的对象。
-
**VO:**显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
《阿里巴巴Java开发手册》可在公众号免费获取资源
一般常用到上面三种领域模型,这三种领域模型全放到interface模块中合适吗?
答案显然是否定的,DO类是与数据库一一映射的,只有dao层才会请求数据库返回DO类对象。而interface模块的目的是暴露出api给其他服务调用,那么api层更应该返回一个DTO对象而不会返回DO对象。那么也就是说interface模块一定不会需要用到DO类,于是我们得出结论:DO类应该在service模块中,而除了DO类的其他领域对象留在interface模块中,于是我们终于得到最终的架构图了!

如果您觉得该文章有用,欢迎点赞、留言并分享给更多人。感谢您的支持!