title: 基础微服务项目架构构建总结
date: 2022-11-16 14:08:04
tags:

  • 微服务
  • IDEA
    categories:
  • 开发实践
    cover: https://cover.png
    feature: false

1. Maven 依赖版本管理

1.1 Maven 依赖的优先级

1、最短路径优先

  • 工程中依赖了 B、C 两个 jar 包
  • 在 B jar 包内引用了 C jar 包版本为 1.0
  • 在工程内直接引用的 C jar 包版本为 2.0

Project -> B -> C(1.0) ,Project -> C(2.0)。由于 C(2.0) 路径最短,所以项目使用的是 C(2.0)

2、POM 申明顺序优先

如果 project -> B -> C(1.0) ,project -> D -> C(2.0) 这样的路径长度一样怎么办呢?

这样的情况下,Maven 会根据 POM 文件声明的顺序加载,如果先声明了 B,后声明了 D,那就最后的依赖就会是 C(1.0)

1.2 Maven 包版本控制

在项目顶层的父 POM 中可以定义如下的三方 jar 的版本定义(这里的 jar 包只是定义,并没有引用,即不会下载依赖)

<dependencyManagement>
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.1.43</version>
    </dependency>
</dependencyManagement>

这样需要引用这个 jar 的子模块可以忽略版本定义直接引用(此时才真正下载依赖)

<dependencies>
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

1.3 多项目全局管理

随便打开一个 SpringBoot 的项目,打开 POM 文件,父级依赖都是

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>${springboot-version}</version>
</parent>

点击这个 parent 的依赖进去看到顶级的父级依赖为

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>${springboot-version}</version>
</parent>

继续点进去,里面已经没有任何 jar 的实际引用了,只有各种各样的 SpringBoot 或者 Spring 生态可能会依赖到的 jar 包的版本定义

Spring 通过定义一个顶层的父级版本依赖,只要是符合 SpringBoot 大版本下的 Spring 组件内的各个 jar 版本都是统一的,如果出现依赖升级的情况,不需要再去升级一个个组件的版本,直接升级父级的依赖管理 POM 中的版本即可

参照 Spring Maven 版本管理的思路,我们也可以定义这样一个业务的顶层 Maven 版本管理工程,如 common-dependency

  1. 版本管理工程的 POM 的父 POM 依赖 spring-boot
  2. 版本管理工程的 POM 内定义业务通用的一些 Maven 依赖版本
  3. 推送该工程至中央仓库(本地可以直接执行 maven install 打包到本地仓库)
  4. 业务应用将父 POMspring-boot 切换为 common-dependency

即在单个业务项目上抽离出一个版本管理工程作为父工程,所有的项目都使用统一的通用的依赖版本,假如某个项目需要自定义依赖或依赖版本,在项目的顶层 POM 文件中再进行定义即可,根据依赖优先级,会优先使用项目的 POM 文件中自定义的依赖版本

1.4 common-dependency

1、新建一个项目

在这里插入图片描述

在这里插入图片描述

2、删掉多余的其他文件

在这里插入图片描述

3、进行版本管理,父依赖为 spring-boot-starter-parent

注:这里 build 里的 spring-boot-maven-plugin 插件只是为了演示同样有依赖版本继承所以才在此处定义(注释状态),该插件只需定义在项目的主启动类的 POM 文件里即可。单模块项目不影响,假如为多模块项目,打包时会报错 Unable to find main class

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
    </parent>

    <groupId>fan</groupId>
    <artifactId>common-dependency</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <modules>
        <module>demo</module>
    </modules>

    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <name>common-dependency</name>
    <description>common-dependency</description>

    <!-- 统一管理jar包版本 -->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <lombok.version>1.18.24</lombok.version>
        <mybatis.plus.version>3.5.1</mybatis.plus.version>
        <druid.version>1.2.9</druid.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- MySql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.19</version>
            </dependency>

            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>

            <!-- Mybatis-Plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>
            <!-- 动态数据源 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>

            <!-- Druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

<!--
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
-->
</project>

4、推送到中央仓库,本地的话 maven install 到本地仓库

在这里插入图片描述

5、其他项目父依赖改为 common-dependency,即可进行统一的依赖版本管理

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fan</groupId>
        <artifactId>common-dependency</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>fan</groupId>
    <artifactId>common-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <name>common-project</name>
    <description>common-project</description>

    <!-- 统一通用的依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

依赖结构如下,不同的颜色的框表示不同的层级,或者说独立工程。如 common-dependency 为通用的顶层版本依赖工程,与 project 是独立的。project 将父依赖设置为 common-dependency,其内部模块的父依赖即相应的项目顶层 POM,module 是包含在 project 里的

在这里插入图片描述

单项目的话可以直接在项目顶层 POM 中进行版本控制即可,即从上图的 project 开始,或者说直接将 common-dependency 当成 project,这时 common-dependency 应该改为对应的项目名

在这里插入图片描述

在这里插入图片描述

2. 项目内划分模块

2.1 分模块

业务模块划分没有一个严格的业界标准,也没有说一定要按照怎么设计,这里根据个人使用总结为以下几个模块,具体使用可根据情况自己进行调整:

Maven 模块模块描述特殊说明
api将 rpc 相关的接口、所必须的交互实体、枚举等定义在此处,提供给内部其他系统进行服务调用单体服务可去除此模块
base/comm与业务无关的通用配置定义在此处,如统一结果返回类、统一工具类等。具有业务无关性,与业务相关的工具类、枚举等可定义在具体的业务模块内
rpcapi 包的 rpc 接口定义实现,一般来说是调用模块内的具体业务接口进行相关的处理单体服务可去除此模块
service具体的服务模块,进行业务处理,不特指某一个模块名
web在此处定义启动类,配置文件(resources 目录),配置类(RedisConfig/MyBatisConfig)等项目配置

依赖结构如下,在之前的基础上添加项目内部划分的模块间的依赖关系

在这里插入图片描述

此处同样可以将上述划分好模块的 project 抽离出来成一个 common-project 用于多项目的统一的通用配置

2.2 common-project

1、同样的新建一个项目

在这里插入图片描述

2、删除其他多余文件,并按照前面的模块划分创建好对应的模块

在这里插入图片描述

3、顶层 POM 如下,父依赖为 common-dependency,并把各个模块依赖进去

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fan</groupId>
        <artifactId>common-dependency</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <modules>
        <module>common-api</module>
        <module>common-base</module>
        <module>common-rpc</module>
        <module>common-service</module>
        <module>common-web</module>
    </modules>

    <groupId>fan</groupId>
    <artifactId>common-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <name>common-project</name>
    <description>common-project</description>

    <!-- 统一通用的依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <!-- 将各模块依赖进来 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>fan</groupId>
                <artifactId>common-api</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

            <dependency>
                <groupId>fan</groupId>
                <artifactId>common-base</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

            <dependency>
                <groupId>fan</groupId>
                <artifactId>common-rpc</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

            <dependency>
                <groupId>fan</groupId>
                <artifactId>common-service</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

            <dependency>
                <groupId>fan</groupId>
                <artifactId>common-web</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4、同时根据依赖关系依赖对应的模块

如 common-api 依赖 common-base

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common-project</artifactId>
        <groupId>fan</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
  
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-api</artifactId>
    <name>common-api</name>

    <dependencies>
        <dependency>
            <groupId>fan</groupId>
            <artifactId>common-base</artifactId>
        </dependency>
    </dependencies>
</project>

common-rpc 又依赖 common-api。由于 common-api 已经依赖了 common-base,所以不需要重复引入 common-base

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common-project</artifactId>
        <groupId>fan</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-rpc</artifactId>
    <name>common-rpc</name>

    <dependencies>
        <dependency>
            <groupId>fan</groupId>
            <artifactId>common-api</artifactId>
        </dependency>
    </dependencies>
</project>

common-service 依赖 common-base

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common-project</artifactId>
        <groupId>fan</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-service</artifactId>
    <name>common-service</name>

    <dependencies>
        <dependency>
            <groupId>fan</groupId>
            <artifactId>common-base</artifactId>
        </dependency>
    </dependencies>
</project>

同样推送到中央仓库,本地的话 maven install 到本地仓库

3. 业务模块内分层

3.1 分层

目前很多业务系统都是基于 MVC 三层架构来开发的,MVC 三层架构中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:展示层、逻辑层、数据层。MVC 三层开发架构是一个比较笼统的分层方式,落实到具体的开发层面,很多项目也并不会 100% 遵从 MVC 固定的分层方式,而是会根据具体的项目需求,做适当的调整

很多 Web 或者 App 项目都是前后端分离的,后端负责暴露接口给前端调用。这种情况下,一般就将后端项目分为 Repository 层、Service 层、Controller 层。其中,Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口。这里的 Service 层假如业务复杂,可再细分为如下三层:

  • Manager 层: 负责将 Dao 层中的数据库操作组合复用,主要是一些缓存方案,中间件的处理,以及对第三方平台的封装
  • Service 层: 更加关注业务逻辑,是业务处理层,将 Manager 组合过的操作和业务逻辑组合在一起,再封装成业务操作
  • Biz 层: 包含 Service 层,Service 层注重基础业务的处理,Biz 层是复杂应用层的业务层

当然,这只是其中一种分层和命名方式。不同的项目、不同的团队,可能会对此有所调整。不过,万变不离其宗,只要是依赖数据库开发的 Web 项目,基本的分层思路都大差不差

3.2 数据载体划分

3.2.1 PO(Persistant Object)/Entity

持久化对象,通过 DAO 层向上传输的数据源对象,实体属性与表字段一一对应。简单来说 PO 就是数据库中的记录,一个 PO 的数据结构对应着库中表的结构,表中的一条记录就是一个 PO 对象,通常 PO 里面除了 getter,setter 之外没有别的方法。概念与 Entity 一致

3.2.2 BO(Business Object)

业务对象,由 Service 层输出的封装业务逻辑的对象。BO 即 PO 的组合,如 PO1 是交易记录,PO2 是商品浏览记录,PO3 是添加购物车记录,等等组合起来形成 BO ,就是个人网站行为对象

一类业务就会对应一个 BO,数量上没有限制,而且 BO 会有很多业务操作,也就是说除了 getter,setter 方法以外,BO 会有很多针对自身数据进行计算的方法

现在很多持久层框架自身就提供了数据组合的功能,因此 BO 有可能是在业务层由业务来拼装 PO 而成,也有可能是在数据库访问层由框架直接生成

3.2.3 DO

DO 主要有两种定义

  • 一种在阿里巴巴开发手册中的定义,DO( Data Object),等同于上面的 PO
  • 一种是在 DDD(Domain-Driven Design)领域驱动设计中,DO(Domain Object),等同于上面的 BO

3.2.4 DTO(Data Transfer Object)

数据传输对象,这个传输通常指的前后端之间的传输,Service 或 Manager 向外传输的对象

BO 和 DTO 的区别

这两个的区别主要是就是字段的删减。BO 对内,为了进行业务计算需要辅助数据,或者是一个业务有多个对外的接口,BO 可能会含有很多接口对外所不需要的数据,而 DTO 在 BO 的基础上,只要自己需要的数据,然后对外提供。在这个关系上,通常不会有数据内容的变化

现在微服务盛行,服务和服务之间调用的传输对象能叫 DTO 吗?

DTO 本身的一个隐含的意义是要能够完整的表达一个业务模块的输出,如果服务和服务之间相对独立,那就可以叫 DTO;如果服务和服务之间不独立,每个都不是一个完整的业务模块,拆开可能仅仅是因为计算复杂度或者性能的问题,那这就不能够叫做 DTO,只能是 BO

3.2.5 VO(Value Object)

数据展示对象,通常是 Web 向模板渲染引擎层传输的对象,字段值与前端要求的字段名称保持一致。即 JSON 里的数据对象

VO 和 DTO 的区别

对于绝大部分的应用场景来说,DTO 和 VO 的属性值基本是一致的,而且它们通常都是 POJO,因此没必要多此一举,但这是实现层面的思维,对于设计层面来说,概念上还是应该存在 VO 和 DTO,因为两者有着本质的区别,DTO 代表服务层需要接收的数据和返回的数据,而 VO 代表展示层需要显示的数据

通常可能的区别如下:

  • 字段不一样,假如这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,VO 可能会根据需要删减一些字段
  • 值不一样,VO 会根据需要对 DTO 中的值进行展示业务的解释

比如服务层有一个 getUser() 的方法返回一个系统用户,其中有一个属性是 gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定

  • DTO 可能是这样的:{"gender": "男", "age": 35}
  • 经过业务解释的 VO 是这样的:{"gender":"帅哥", "age": "30~39"}

这时可能说,在服务层直接就返回“帅哥美女”不就行了吗?

对于大部分应用来说,这不是问题,但如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从单一职责原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的 DTO,不应该出现与表现形式的耦合

3.2.6 Query

数据查询对象,各层接收上层的查询请求,超过 2 个参数的查询封装,禁止使用 Map 类来传输

3.3 结构图

示例结构图如下,个人理解可能不一样,可根据具体情况进行调整

在这里插入图片描述

4. 项目实践

4.1 结构图

这里由于是单体服务,去掉了 api 和 rpc 模块,假如有多个服务需要互相调用的话,加上 api 和 rpc 模块同样依赖对应的 common-api 和 common-rpc 模块即可,同时服务模块都依赖 common-service

在这里插入图片描述

4.2 实践

1、按照上面的模块划分,创建好项目

在这里插入图片描述

2、依赖 common-project 对应的模块

如 resource_nav_comm 依赖 common-base

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<parent>
		<groupId>fan</groupId>
		<artifactId>ResourceNavigation</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<groupId>fan</groupId>
	<artifactId>resource_nav_comm</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<modelVersion>4.0.0</modelVersion>
	<name>resource_nav_comm</name>
	<description>resource_nav_comm</description>

	<dependencies>
		<dependency>
			<groupId>fan</groupId>
			<artifactId>common-base</artifactId>
		</dependency>
	</dependencies>
</project>

resource_nav_web 依赖 common-web

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fan</groupId>
        <artifactId>ResourceNavigation</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>resource_nav_web</artifactId>
    <name>resource_nav_web</name>

    <dependencies>
        <dependency>
            <groupId>fan</groupId>
            <artifactId>common-web</artifactId>
        </dependency>
    </dependencies>
</project>

resource_nav_system 模块依赖 common-service

3、然后再根据上面的模块内分层,在对应层进行相应的开发

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐