推荐阅读(点击即可跳转阅读)
1. SpringBoot内容聚合
2. 面试题内容聚合
3. 设计模式内容聚合
4. 排序算法内容聚合
5. 多线程内容聚合
由于近年来的移动端的发展和 2C模式 的红利,一些在风口的企业的业务得到爆发式增长。从架构层面来说,业务驱动技术的变革,所以微服务架构的概念得到很多企业的青睐,因为可以解决服务的大流量和高并发以及稳定性的要求。
但是任何架构设计不是一蹴而就的,不能从起步就开始使用微服务,一般都是先通过单体架构来快速实现需求和抢占市场,然后再迭代式扩展。不能一口气吃个胖子。
这几年自己有经历从单体到微服务的架构演变,也有直接参与到已经落地的微服务架构的项目中。见过好的架构设计,也见过一些孬的设计。好的架构设计,代码结构优雅,分层清晰,业务边界划分明朗,业务开发人员职责清晰。不好的设计就会导致代码混乱难以维护,对新需求无法快速应变,开发人员容易在补丁上打补丁,最后造成积重难返不得不重构。
架构师需要从业务层面和未来业务发展有个全面的规划,让架构高可用,易扩展,灵活易使用,隐藏其复杂性。好的架构会让下面的业务开发人员按照既定的模式“傻瓜式”编程。
既然第一步是单体架构,那么好的单体架构设计,为我们后期的微服务拆分会有事半功倍的效果。避免重复劳动和过多的重写。我们可以从这些方面进行一些有效的设计。

1、划清业务边界

如果对未来的架构有微服务的考虑,那么在单体架构的时候就需要理清业务边界的问题,常见的简单划分就是以业务区分,例如:用户,商品,订单,支付,权限等等,具体的拆分程度可根据自身业务量和需要做划分。
当前流行的 DDD(领域驱动设计)可以作为一个指导原则,但是 DDD 比较偏向于理论,需要执行人员有良好的专业能力才能实施的比较好。

2、代码层次结构

业务区分好之后,就是项目代码模块的设计。在代码层我们需要根据MVC的模式,建议的代码设计层次如下:

  1. ├─demo-common
  2. demo-common.iml
  3. pom.xml
  4. └─src
  5. ├─main
  6. ├─java
  7. └─resources
  8. └─test
  9. └─java
  10. ├─demo-dao
  11. demo-dao.iml
  12. pom.xml
  13. └─src
  14. ├─main
  15. ├─java
  16. └─resources
  17. └─test
  18. └─java
  19. ├─demo-service
  20. demo-service.iml
  21. pom.xml
  22. └─src
  23. ├─main
  24. ├─java
  25. └─resources
  26. └─test
  27. └─java
  28. └─demo-web
  29. demo-web.iml
  30. pom.xml
  31. └─src
  32. ├─main
  33. ├─java
  34. └─resources
  35. └─test
  36. └─java

主要包含4个 module 模块

  • demo-common:基础模块,枚举,常亮类,工具类,配置类。
  • demo-dao:Dao层,mapper接口,mapper.xml。
  • demo-service:服务接口提供层,业务service接口。
  • demo-web:web层,Controller类,服务接口,与外部进行交互。

各模块之间的依赖关系为:
1. 从单体架构升级到微服务,在代码层面应注意的一些问题(2019.7) - 图1
项目 Module 模块设计完成之后,每个模块的内部 package 包如何设计呢?通常有两种划分模式:根据业务模块然后内部按MVC划分,根据MVC模式然后内部按业务划分。
1、根据业务模块划分,就是将每个业务模块作为一个 package,然后每个package里面有自己的 MVC,这样就做到业务模块的隔离。
1. 从单体架构升级到微服务,在代码层面应注意的一些问题(2019.7) - 图2
2、根据 MVC 模式划分,先根据 MVC模式划分不同的包,service,serviceImpl,dto等,然后再是各个业务自己的模型和服务接口。
1. 从单体架构升级到微服务,在代码层面应注意的一些问题(2019.7) - 图3
针对上述的两个划分模式,个人的选择是根据业务模式划分,这样的包设计与后期微服务拆分有良好的匹配度,拆分的时候只需要将每个业务包下的代码 Copy 到新的微服务中就行了,易迁移变动小。每个模块中对不同的业务通过 package 包名进行划分。
例如:com.example.jajian.service.order、com.example.jajian.service.user等。

  1. └─src
  2. ├─main
  3. ├─java
  4. └─com
  5. └─example
  6. └─jajian
  7. ├─common
  8. BaserService.java
  9. └─service
  10. ├─order
  11. ├─dto
  12. OrderDto.java
  13. └─service
  14. OrderService.java
  15. └─impl
  16. OrderServiceImpl.java
  17. ├─pay
  18. ├─dto
  19. PayDto.java
  20. └─service
  21. PayService.java
  22. └─impl
  23. PayServiceImpl.java
  24. └─user
  25. ├─dto
  26. UserDto.java
  27. └─service
  28. UserService.java
  29. └─impl
  30. UserServiceImpl.java
  31. └─resources
  32. └─test
  33. └─java

这样划分有什么好处?我们单体架构的时候这样开发,当需要拆分成微服务的时候就可以直接将业务包拆分出去,因为每个业务包里面就已经包含了所有的当前业务的关联业务类。

3、避免多边界业务的关联查询

单表关联由于业务需要而且简单方便易使用,所以多表关联查询在单体服务中是普遍存在的,如果我们后期不需要做服务拆分则可以不需要考虑这方面的限制。
但是如果后期有微服务的规划,那么单体服务的时候如果没有做这个方面的限制,mybatis 的 mapper.xml中有过多的多表关联查询,这些关联查询会严重影响服务拆分的进度和复杂度。
如果同属于一个业务领域则可以使用关联查询,而那些微服务拆分后属于不同领域的业务则应避免使用多表关联查询,因为不同的业务领域后期会被隔离拆分到不同的服务当中,即数据库表都是分布在不同的服务器上,所有服务之间都是通过RPC方式进行通信,关联查询这时是无法处理的。

4、Controller层尽量不做业务逻辑处理

常看到很多 coder 会在Controller 层做一些业务处理,个人认为这是很不规范的。Controller层是控制层,是和前端进行数据转换的,这里我们应该只做请求的接受和返回,也无需做一些异常的try…catch…的捕获,异常可以通过全局通用拦截器统一进行拦截然后返回给前端异常提示语,提升代码的简洁性。
所有的参数校验也放到 service层,因为如果服务内部调用也可以使用提高代码的共用度。当然分层领域模型最好也能区分开,
DO(Data Object):此对象与数据库表结构—对应,通过DAO层向上传输数据源对象。
DTO(Date Transfer Object):数据传输对象,service或Manager向外传输的对象。
VO(View Object):显示层的对象,通常是Web向模板渲染引擎层传输的对象。
这样区分开的好处是,当你需要对展示层数据进行特殊定制化的时候可以灵活变通,例如针对用户隐私信息身份证号,手机号码脱敏处理,或者用户ID加密显示等。
最后就是统一通用返回类了,通过这种格式的封装我们将数据格式进行全局格式化,这里的状态码可以自己设计的更详细一点。

  1. public class CommonResult<T> {
  2. public static final String CODE_SUCCESS = "0";
  3. public static final String CODE_FAILED = "9999";
  4. private String code;
  5. private T data;
  6. private String msg;
  7. private CommonResult(String code, T data, String msg) {
  8. this.code = code;
  9. this.data = data;
  10. this.msg = msg;
  11. }
  12. public boolean isSuccess() {
  13. return CODE_SUCCESS.equals(code);
  14. }
  15. public static <T> CommonResult<T> success() {
  16. return new CommonResult<>(CODE_SUCCESS, null, null);
  17. }
  18. public static <T> CommonResult<T> success(T data) {
  19. return new CommonResult<>(CODE_SUCCESS, data, null);
  20. }
  21. public static <T> CommonResult<T> success(T data, String msg) {
  22. return new CommonResult<>(CODE_SUCCESS, data, msg);
  23. }
  24. public static <T> CommonResult<T> failed() {
  25. return new CommonResult<>(CODE_FAILED, null, null);
  26. }
  27. public static <T> CommonResult<T> failed(String errorCode, String msg) {
  28. return new CommonResult<>(errorCode, null, msg);
  29. }
  30. public static <T> CommonResult<T> failed(String msg) {
  31. return new CommonResult<>(CODE_FAILED, null, msg);
  32. }
  33. public static <T> CommonResult<T> failed(T data, String msg) {
  34. return new CommonResult<>(CODE_FAILED, data, msg);
  35. }
  36. public static <T> CommonResult<T> failed(String errorCode, T data, String msg) {
  37. return new CommonResult<>(errorCode, data, msg);
  38. }
  39. // 省略 setter、getter
  40. }

5、后记

以上只是列举了单体服务未来规划做微服务时需要注意的一部分简单内容,每个人在做单体架构拆分成微服务的时候都会踩到各种各样的坑,这些坑成了我们的开发经验,有了这些坑就会形成注意点,在我们下次开发时就会具有指导意义。也许我们程序员就是在踩坑和填坑的过程中成长壮大起来的。

转载 https://mp.weixin.qq.com/s/ZHeMvksRQVJCxhDidmxIeA
转载 https://www.yuque.com/yinjianwei/yc65uq/czfxby