开始本章之前默认读者已经配置好了以下环境:Intellij IDEA 2022.1.2、JDK 1.8.0_144、Maven 3,另外也建议大家在一些免费代码托管平台开个帐号,这样就可以免费使用git做版本处理了,笔者自己私人使用的是阿里云的云效平台。因为此专题涉及到代码,所以笔者暂时把工程名称定为【base-grpc-framework】。
本章代码任务:1、构建工程目录;2、完成springboot集成模块;
一、为什么工程结构需要设计?
工程的构建工作一般很容易被忽略,通常认为只是创建几个目录存储代码用而已。其实不然,工程目录结构合理与否除了直接会影响工程打包和上线流程,还会影响开发效率以及重构。
工程目录设计除了符合系统架构外,还要符合小组成员的开发习惯。所以在开发新项目或是优化祖传代码时一定要把这件事做为一件非常重要的事来对待,否则过程中重构工程结构的代价会非常大,想想N多代码需要迁移、多个分支在开发、还要大量的回归测试等等。
二、两种典型的工程目录结构
2.1、普通的分布式工程结构
大多数项目组都会采用这种格式(在这个系列专题中笔者用到的是这种结构),这种结构一般比较适用单体应用或是不太复杂的分布式应用,其特点是把API定义和API实现隔离开。其它模块都可看成是辅助模块被包装或是被引用,其结构如下:
其调用关系大致如下,比较简单不再详细描述了,同学们理解下就可以:
2.2、组件化的分布式工程结构
这种应用比较复杂,一般是为了满足一套代码多地部署而设计的。其特点是多地部署时其核心代码在逻辑上存在差别。比如电商中的结算流程,在不同国家都有法律上的明确要求。
结算流程在中国需要遵守二分帐期的法规要求,但这个法规只局限于中国,比如在泰国就没有。在用同一套代码实现时就需要在部署泰国时把二分这个逻辑拿掉,一般有两种方案:1、维护两套源码;2、把差异点隔离出来,按需打包;在综合考虑成本时一般在维护大型系统时一般都会采用第2套方案。下同给出两个例子,这两个例子可以组合使用以适应更多场景。
这种方式不在本专题内容范畴内,所以只简单介绍下,感觉举的读者可在笔者写的《DDD领域驱动设计》专题中了解。
上图红框内的模块区分了国别,承载了不同国家的定制化业务代码,cn-中国,id-印尼,th-泰国。
上图红框内的模块区分了业务,承载了不同业务方的定制化代码,比如虚拟商品区别于实物商品的物流和仓储流程。
综上:如果在建设中台系统或是跨国系统时可以采用2.2节中的工程设计,建设普通的业务类系统或单体应用时可采用2.1节中的工程设计。
三、工程详细
3.1、总体结构
为了有个全局视角,笔者把后续需要用到的模块一次性全创建了,如下图所示,这里只需了解下全貌即可,后续章节笔者会带着大家一点点填内容:
- pom.xml:项目主maven文件,主要定义公共的配置以及版本控制;
- .gitignore:用git时忽略的提供文件配置;
- base-grpc-framework-common:项目工具包;
- base-grpc-framework-api:项目接口定义;
- base-grpc-framework-core:接口业务实现;
- base-grpc-framework-dao:数据库存储实现;
- base-grpc-framework-application:项目启动包装应用;
- base-grpc-framework-client:web客户端;
四、创建过程
本节中我们要实现上一节中提到的工程结构,在特殊的地方笔者会详细说明,通用的地方大家按图示自行创建即可在后续实现代码时再完善其内容。因此专题的代码笔者一直在写,部分源码已早于文档内容并可能存在错误,所以暂时先不共享了,在此专题结束时会统一放出来。实在有需要的同学可以私信笔者,不过笔者强烈建议大家随着笔者一起来动手。
笔者用的是IDE是idea,建议大家使用2022的版本,优化了很多便捷的操作。必要的插件可参考笔者的 https://blog.51cto.com/arch/5277533 一文,先安装即可后续应该全会用到了。完整的工程目录下如:
4.1、创建root工程
root工程,只有一个pom.xml文件,主要为了控制版本和一些公共的配置(可查看下面的源码注释),打包为pom类型。文档中所有的代码都是经过笔者测试过的,可复制后直接使用,完整代码如下:
<?xml versinotallow="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">
<modelVersion>4.0.0</modelVersion>
<!--工程定义-->
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<!--引入springboot框架-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.2.RELEASE</version>
</parent>
<!--版本定义:插件相关、配置相关、工具框架、grpc、数据库、其它杂项-->
<properties>
<java.version>1.8</java.version>
<maven.compile.source>1.8</maven.compile.source>
<maven.compile.target>1.8</maven.compile.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.kr.motd>1.6.2</maven.kr.motd>
<maven.org.xolstice>0.6.1</maven.org.xolstice>
<nacos.version>0.2.8</nacos.version>
<junit.test.version>4.12</junit.test.version>
<hutool.version>5.7.21</hutool.version>
<lombok.version>1.18.20</lombok.version>
<guava.version>30.1-jre</guava.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
<grpc.version>2.13.0.RELEASE</grpc.version>
<io.grpc.version>1.42.1</io.grpc.version>
<protobuf.java.version>3.19.1</protobuf.java.version>
<mybatisplus.version>3.4.1</mybatisplus.version>
<mysql.version>8.0.23</mysql.version>
<druid.version>1.1.22</druid.version>
<commons-lang3.version>3.9</commons-lang3.version>
</properties>
<dependencyManagement>
<dependencies>
<!--nacos应用配置-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos.version}</version>
</dependency>
<!--公共的工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!--grpc相关-->
<!--用于生成源代码的辅助类,供API模块使用-->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${io.grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${io.grpc.version}</version>
</dependency>
<!--用于服务端和客户端引用-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>${grpc.version}</version>
</dependency>
<!--proto生成java代码的工具库,建议三端都引用-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
<!--数据库相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<!--三方仓库-->
<repositories>
<repository>
<id>central</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<modules>
<module>base-grpc-framework-common</module>
<module>base-grpc-framework-api</module>
<module>base-grpc-framework-core</module>
<module>base-grpc-framework-dao</module>
<module>base-grpc-framework-application</module>
<module>base-grpc-framework-client</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compile.source}</source>
<target>${maven.compile.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2、创建.gitignore免提交git控制文件
建议用git做为版本控制工具,文件内容如下:
# Folder
/target/
*/target/*
*.jar
/logs
/out/
*.log
!.mvn/wrapper/maven-wrapper.jar
# Ide and OS
.idea
*.iws
*.iml
*.ipr
*.DS_Store
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# sts
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
4.3、创建base-grpc-framework-application服务启动模块
这个模块主要集成了springboot用于启动应用,没有任何业务逻辑,依赖core模块。隔离出来的原因是为了在更改部署和启动方式时不需要更改业务代码,比如可以再创建一个tomcat模块,用于在tomcat环境下部署。工程结构如下:
4.3.1、pom.xml配置
springboot的版本有时很容易出现jar包冲突和不兼容的问题,尤其和其它框架集成时。在官网上给了一些基线配置,读者可自行查阅,笔者用的是2.2.2.RELEASE版本(在本专题中不建议读者升级,因为后续集成其它框架时可能会带来冲突)。
在引入spring-boot-starter包时,如果后续要使用更高版本的log4j做为日志框架时,需排队logging模块,否则会有包冲突问题,如下所示:
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-application</artifactId>
<packaging>jar</packaging>
<properties>
<swagger.version>1.9.0.RELEASE</swagger.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zd</groupId>
<artifactId>base-grpc-framework-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!--生产环境建议去掉或封掉swagger功能-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
</dependencies>
</project>
4.3.2、BaseFrameworkApplication启动类
注意@SpringBootApplication注解中的属性,笔者的类路径是是com.zd开头的。类路径:
/**
* @Title: com.zd.baseframework.BaseFrameworkApplication
* @Description 启动类,此类注解的值内容可以从外部的配置系统中配置
* @author liudong
* @date 2022/6/13 10:41 PM
*/
@SpringBootApplication(scanBasePackages = {"com.zd.baseframework", "cn.hutool.extra.spring"})
@MapperScan({"com.zd.baseframework"})
@EnableSwagger2
@EnableConfigurationProperties
public class BaseFrameworkApplication {
public static void main(String []args){
SpringApplication.run(BaseFrameworkApplication.class, args);
}
}
4.3.3、SwawggerConfig框架
笔者为了偷懒一下,集成swawagger了框架只是为了测试Restful接口用,建议在线上环境中不要集成这个框架。开发测试时可以使用postman软件或是idea插件,源码如下,注意下.apis(basePackage("com.zd"))中的配置,笔者的类路径是是com.zd开头的:
/**
* @Title: com.zd.baseframework.config.SwaggerConfig
* @Description 用于swagger测试:访问地址:http://localhost:8080/swagger-ui.html
* @author liudong
* @date 2022/6/15 6:07 PM
*/
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
// 添加head参数start
ParameterBuilder tokenPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
tokenPar.name("Authorization").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(tokenPar.build());
// 配置swagger
Docket result = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(basePackage("com.zd.baseframework.core.controller.core"))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(pars);
result.enable(true);
return result;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger-api文档")
.description("用于开发测试,线上环境需要删除掉")
.termsOfServiceUrl("http://localhost:15103")
.version("1.0")
.build();
}
public static Predicate<RequestHandler> basePackage(final String basePackage) {
return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
}
private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
return input -> {
// 循环判断匹配
for (String strPackage : basePackage.split(";")) {
boolean isMatch = input.getPackage().getName().startsWith(strPackage);
if (isMatch) {
return true;
}
}
return false;
};
}
private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
return Optional.fromNullable(input.declaringClass());
}
}
4.3.4、application.yml应用配置文件
springboot启动时默认读取名为application.yml文件。一般开发时都会有多个环境,自然也就存在了多个配置文件,笔者在这里只配置了开发和发布两个配置文件,内容暂时保持一致。
文件目录:src/main/resources/application-dep.yml和application-dev.yml
# http配置
server:
compression:
enabled: true
mime-types: application/json,application/octet-stream
# spring配置
spring:
application:
name: GrpcFramework-Server-APP
4.3.5、启动测试
配置启动类为dev,如下图所示:如果没有环境变量(E)街面,可先点选【修改选项】勾上环境变量一项:
运行BaseFrameworkApplication.java类,不出意外的话可以在控制台看到如下输出。
打开浏览器,测试swawgger是否可正常访问,如下图所示:
此至本章主要内容就完成了,后面几个小节是为了后面内容做准备,建议大家先把工程创建了。
4.4、创建base-grpc-framework-api接口定义模块
在本专题中用proto来进行接口定义,优点是可以跨语言。其原理也比较简单,就是在编码期间通过插件编译成所需要的语言,proto是本系列一个非常重要的知识点后面会详细说明,本章只创建一个模块目录即可:
pom.xml配置如下:
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-api</artifactId>
<packaging>jar</packaging>
<project>
4.5、创建base-grpc-framework-core模块
本章只创建一个模块目录即可,如下图所示:
pom.xml配置如下:
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-core</artifactId>
<packaging>jar</packaging>
<project>
4.6、创建base-grpc-framework-dao模块
数据库实现,暂时只按如下方式创建即可。
pom.xml配置如下:
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-dao</artifactId>
<packaging>jar</packaging>
<project>
4.7、创建base-grpc-framework-common模块
只按如下方式创建即可。后面再填加内容:
pom.xml配置如下:
<?xml versinotallow="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>base-grpc-framework-parent</artifactId>
<groupId>com.zd</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-grpc-framework-common</artifactId>
<packaging>jar</packaging>
<project>
五、GPRC的测试方法
5.1、grpcurl命令行测试
brew install grpcurl
//查看GRPC服务所有的服务列表
grpcurl --plaintext 127.0.0.1:9898 list
输出如下:
/*grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
net.devh.boot.grpc.example.MyService
*/
//查看某一个接口提供的服务接口
grpcurl --plaintext 127.0.0.1:9898 list net.devh.boot.grpc.example.MyService
输出如下:
/*net.devh.boot.grpc.example.MyService.SayHello*/
//模拟访问
grpcurl --plaintext -d '{"name": "test"}' 127.0.0.1:9898 net.devh.boot.grpc.example.MyService/sayHello
5.2、grpcui图形化测试(推荐)
brew install grpcui #安装
grpcui -plaintext 127.0.0.1:9898 #运行