Gradle子项目划分与微服务的代码结构

Gradle简介

如前序章节微服务技术栈概览所述,本书选用Java作为开发语言、Gradle作为构建工具。

与Maven相比,Gradle具有如下优势:

  • 灵活性:Gradle内置了脚本支持,可以实现更强大、更灵活的构建功能。
  • 高性能:Gradle支持并行编译、多级缓存,最高可节省90%的编译时间[^1]。
  • 易于维护:与xml相比,Gradle的依赖描述语言更简洁,更易于维护。
  • 无缝兼容:Gradle无缝兼容Maven,已有的系统也可以轻松地迁移过来。

微服务架构下Gradle的子项目划分

微服务的自动发现与负载均衡一章中,我们已经构建了一个微服务项目”lmsia-abc”,让我们来看一下它的目录结构。为了清晰起见,只展示一层目录结构:

  1. .
  2. ├── build.gradle
  3. ├── gradle
  4. └── wrapper
  5. ├── gradlew
  6. ├── gradlew.bat
  7. ├── lmsia-abc-client
  8. ├── build
  9. ├── build.gradle
  10. ├── out
  11. └── src
  12. ├── lmsia-abc-common
  13. ├── build
  14. ├── build.gradle
  15. ├── out
  16. └── src
  17. ├── lmsia-abc.iml
  18. ├── lmsia-abc-job
  19. ├── build
  20. ├── build.gradle
  21. ├── out
  22. └── src
  23. ├── lmsia-abc-server
  24. ├── build
  25. ├── build.gradle
  26. ├── out
  27. └── src
  28. ├── settings.gradle
  29. └── tool
  30. ├── compileThrift.sh
  31. └── shutdown.sh

我们来逐一进行讲解:

  • 主项目级别Gradle配置文件: build.gradle和settings.gradle,定义了子项目,以及子项目共用的依赖、仓库等,我们会在稍后展开讲解。
  • gradle最小化构建工具: gradle构建工具初始化后,会在项目中生成gradle、gradlew、gradlew.bat,这些是最小化的构建工具,方便项目移植后的构建。
  • lmsia-abc-common: 如前文所属,我们的项目采用Thrift RPC。我们将Thrift的dsl文件、自动生成的Java(客户端桩)代码放置在common子项目中。这样,如果有其他微服务需要依赖相关数据结构,只需要依赖’lmsia-abc-common’即可。
  • lmsia-abc-client: 在引用common包后,可以自行构造Thrift客户端,从而完成RPC调用。然而,这一过程较为繁琐。试想有一个提供用户信息的微服务,因为较为基础,有20个微服务依赖它,那么就需要20次书写重复的代码。”重复代码乃万恶之源”,为了解决Thrift客户端重复生成的问题,我们创建了client子项目,负责生成Thrift客户端,并添加自动配置(如果没有接触过Spring Boot,可能会不理解自动配置,没有关系,我们很快就会作出解释)。
  • lmsia-abc-server: 微服务的核心,即提供”服务”。我们将Thrift、RPC服务的逻辑代码封装在server子项目中。
  • lmsia-abc-job: 在微服务业务的升级、演进过程中,可能会需要对数据作出修正。这些代码可能只需要执行一次,因此不需要放入server子项目提供服务,我们将他们放入job子项目中。
  • tool: 一些提升微服务开发的效率工具,我们将在开发效率脚本一节中进行介绍。

由于篇幅所限,我们不会对Thrift进行入门介绍,如果你无法理解上述Thrift的DSL、自动代码生成等内容,可以参考官方教程

我们来看一下根路径下的build.gradle

  1. buildscript {
  2. ext {
  3. springBootVersion = '1.5.6.RELEASE'
  4. }
  5. repositories {
  6. maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
  7. maven { url 'https://jitpack.io' }
  8. }
  9. dependencies {
  10. classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  11. }
  12. }
  13. subprojects {
  14. apply plugin: 'java'
  15. apply plugin: 'idea'
  16. apply plugin: 'org.springframework.boot'
  17. sourceCompatibility = 1.8
  18. targetCompatibility = 1.8
  19. group = 'com.coder4.lmsia'
  20. version = '0.0.1'
  21. repositories {
  22. maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
  23. maven { url 'https://jitpack.io' }
  24. mavenLocal()
  25. }
  26. }
  27. repositories {
  28. maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
  29. maven { url 'https://jitpack.io' }
  30. }

我们来顺序解释上述文件:

  • buildscript: 定义了gradle自身所需要使用的资源,包含Spring Boot插件和maven的仓库地址。
  • subprojects: 定义了子项目(common, client, job, server)所需要使用的共用部分,Java、IDEA、Spring Boot插件、Javac版本、项目的group, version,以及仓库,这里的仓库是给子项目使用的,看似与buildscript的定义重复,但确实是必要的。
  • repositories: 定义主项需要的仓库地址,与上面类似,这里也是必须的,并不是冗余定义。

在settings.gradle中,定义了各个子项目的路径:

  1. include 'lmsia-abc-common'
  2. include 'lmsia-abc-client'
  3. include 'lmsia-abc-job'
  4. include 'lmsia-abc-server'

下面,我们来看一下子项目中的gradle文件,以’lmsia-abc-server/build.gradle’为例:

  1. dependencies {
  2. compile project(':lmsia-abc-common')
  3. compile 'org.springframework.boot:spring-boot-starter-web'
  4. compile 'com.github.liheyuan:lmsia-thrift-server:0.0.1'
  5. compile 'com.github.liheyuan:lmsia-commons-http:0.0.1'
  6. testCompile 'org.springframework.boot:spring-boot-starter-test'
  7. }

由于我们将子项目共用的部分抽取到根目录的build.gradle中,所以上述子项目的gradle文件就十分简单了。

上述文件表明:server子项目依赖common子项目,同时依赖了’spring-boot-starter-web’、’lmsia-thrift-server’、’lmsia-commons-http’两个项目,测试依赖’spring-boot-starter-test’。细心的读者可能已经发现,’spring-boot-starter-web’和’spring-boot-starter-test’并没有定义版本号。这就是我们在根文件中定义的’Spring Boot插件’所完成的工作之一。

common子项目的代码结构

我们来看一下common子项目的结构:

  1. ├── build.gradle
  2. └── src
  3. └── main
  4. ├── java
  5. └── com
  6. └── coder4
  7. └── lmsia
  8. └── abc
  9. ├── constant
  10. └── LmsiaAbcConstant.java
  11. └── thrift
  12. └── LmsiaAbcThrift.java
  13. └── thrift
  14. └── lmsiaAbc.thrift

我们解释一下目录结构:

  • 除了build.gradle外,代码被放置在src/main/java下,这是gradle推荐的默认路径。
  • thrift的DSL文件放置在’src/main/thrift’下
  • 编译好的Thrift桩文件在’src/main/java`下

client子项目的代码结构

接下来,我们看一下client子项目的目录结构:

  1. ├── build.gradle
  2. └── src
  3. ├── main
  4. ├── java
  5. └── com
  6. └── coder4
  7. └── lmsia
  8. └── abc
  9. └── client
  10. ├── configuration
  11. └── LmsiaAbcClientConfiguration.java
  12. ├── LmsiaAbcEasyClientBuilder.java
  13. └── LmsiaK8ServiceClientBuilder.java
  14. └── resources
  15. └── META-INF
  16. └── spring.factories
  17. └── test
  18. └── java
  19. └── com
  20. └── coder4
  21. └── lmsia
  22. └── abc
  23. └── client
  24. ├── LmsiaAbcEasyClientTest.java
  25. └── LmsiaAbcK8ServiceClientTest.java
  • 自动配置: 代码包的LmsiaAbcClientConfiguration和资源包的spring.factories,一起实现了自动配置。当别的项目通过maven引用这个client包时,配置会自动生效,生成可注入的客户端实例。
  • Builder: 方便手动或自动配置的调用,用于生成客户端实例。
  • 测试: ‘src/test’里面内置了两个测试。

server子项目的代码结构

看一下server子项目的目录结构:

  1. .
  2. ├── build.gradle
  3. └── src
  4. ├── main
  5. ├── java
  6. └── com
  7. └── coder4
  8. └── lmsia
  9. └── abc
  10. └── server
  11. ├── configuration
  12. └── ThriftProcessorConfiguration.java
  13. ├── LmsiaAbcApplication.java
  14. ├── rest
  15. ├── controller
  16. └── AbcController.java
  17. ├── logic
  18. ├── impl
  19. └── AbcLogicImpl.java
  20. └── intf
  21. └── AbcLogic.java
  22. └── wrapper
  23. ├── service
  24. ├── impl
  25. └── HelloServiceImpl.java
  26. └── intf
  27. └── HelloService.java
  28. └── thrift
  29. └── ThriftServerHandler.java
  30. └── resources
  31. ├── application.yaml
  32. └── logback-spring.xml
  33. └── test
  34. └── java
  35. └── com.coder4.lmsia.abc
  36. └── server
  37. └── LmsiaAbcTest.java

解释一下文件:

  • RPC服务相关:
    • 自动配置: ‘server.configuration.ThriftProcessorConfiguration’是RPC服务的自动配置,用于自动启动RPC服务,我们后面会对此详细讲解。
    • RPC入口函数: server.thrift.thrift.ThriftServerHandler定义了RPC的入口函数
  • REST服务:REST服务放在server.rest包下,并进行了进一步分层
    • Spring MVC: Controller在rest.controller下
    • REST逻辑: 为了防止Controller过于臃肿,我们将Controller的逻辑都放在了rest.logic中。该包又分为intf和impl,前者是Interface(接口),后者是Implementation(实现)。
    • Wrapper: 如果Logic中需要对REST接口进行包装,可以放在wrapper里
  • 业务逻辑: 我们将所有业务逻辑抽象出来,放到server.service下,与Logic类似,也分为intf和impl
  • 配置:
    • Spring Boot配置:resources/application.yaml是Spring Boot的配置文件,如服务名、数据库配置等
    • 日志配置:我们使用了默认的logback作为日志系统,配置在resources/logback-spring.xml中
  • 测试用例:test下,与client和common类似,不再赘述。

上述分层看起来有些复杂,但会让各个层次的职责划分的更为清楚,如果你的项目中有更好的方案,也可以采用已有分层结构。

job子项目的代码结构

最后,我们看一下job子项目的目录结构:

  1. ├── build.gradle
  2. └── src
  3. └── main
  4. ├── java
  5. └── com
  6. └── coder4
  7. └── lmsia
  8. └── abc
  9. └── job
  10. ├── LmsiaAbcJob.java
  11. └── LmsiaAbcJobStarter.java
  12. └── resources
  13. ├── application.yaml
  14. └── logback-spring.xml

简单解释下:

  • 命令行入口: 本节开篇部分已经提到,job是可执行程序,LmsiaAbcJobStarter即是命令行的入口。
  • 具体job: 这里只有一个LmsiaAbcJob,会通过参数与入口关联,后续会详细讲解。

至此,我们已经对lmsia这个示例项目的Gradle、子项目划分、子项目结构做了较为详尽的讲解。

需要说明的是:由于篇幅先后关系的问题,server子项目我们并未包含数据库、事件处理的相关文件和目录结构,我们会在后续章节视进度逐渐添加。

[^1]:数据来源自官方性能评测Gradle vs Maven: Performance Comparison