30、综合实战:构建一个完整微服务项目

说实话,到了第三十讲,咱们终于要干一票大的了。前面二十九章,咱们把 Gradle 的边边角角都摸了一遍,从单模块到多模块,从构建脚本到依赖管理,再到 Docker 镜像和 CI/CD。今天这一章,就是把所有零件拧到一起,造一台能跑的车。

我个人习惯,每次学一个新工具,最后一定要做一个“能看见”的东西。光看理论,记不住。你想想看,一个微服务项目,从代码写到镜像,再到自动部署,这一套流程走下来,你对 Gradle 的理解绝对会上一个台阶。

30.1 项目整体结构设计

咱们要构建的是一个电商系统的订单服务。麻雀虽小,五脏俱全。我把它拆成四个模块:

  • order-api:暴露 REST 接口,Spring Boot 启动类放这里。
  • order-service:业务逻辑层,处理订单核心逻辑。
  • order-dao:数据访问层,跟数据库打交道。
  • order-common:公共工具类、DTO、异常定义。

为什么这么拆?我在项目中遇到过,如果所有代码都塞在一个模块里,后期维护就是一场噩梦。尤其是多人协作时,改一个接口可能影响整个服务。拆开之后,每个模块职责清晰,依赖关系也一目了然。

核心原则:模块之间单向依赖。order-api 依赖 order-service,order-service 依赖 order-dao,order-common 被所有模块依赖。千万别搞循环依赖,Gradle 虽然能检测,但跑起来性能会打折扣。

下面这张图,是我用 SVG 画的模块依赖关系,你看一眼就明白了。

order-api (Spring Boot 启动) order-service (业务逻辑) order-dao (数据访问) order-common (公共模块)

30.2 根项目 settings.gradle 配置

多模块项目的起点,就是 settings.gradle。这里有个坑,我曾经漏写了一行,结果子模块死活找不到。你记住,模块名和目录名要一致,不然 Gradle 会迷路。

// settings.gradle
rootProject.name = 'order-platform'

include 'order-common'
include 'order-dao'
include 'order-service'
include 'order-api'

// 如果你有独立的构建缓存配置,也可以加在这里
buildCache {
    local {
        enabled = true
    }
}

30.3 根项目 build.gradle 统一管理

我建议把所有公共配置抽到根项目的 build.gradle 里。比如插件版本、Java 版本、仓库地址。子模块只需要写自己特有的部分。这样做的好处是,升级 Spring Boot 版本时,改一个地方就行。

// 根项目 build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0' apply false
    id 'io.spring.dependency-management' version '1.1.4' apply false
}

allprojects {
    group = 'com.example.order'
    version = '1.0.0-SNAPSHOT'

    repositories {
        mavenCentral()
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'

    java {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    dependencies {
        // 公共依赖,比如 lombok
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
    }

    tasks.withType(Test) {
        useJUnitPlatform()
    }
}

小技巧:apply false 声明插件,然后在子模块里按需应用。这样不会把 Spring Boot 插件强加到所有模块上,比如 order-common 就不需要打成可执行 jar。

30.4 子模块专属配置

每个子模块的 build.gradle 都很精简。以 order-api 为例:

// order-api/build.gradle
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation project(':order-service')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    // 其他运行时依赖
}

// 配置 Spring Boot 的 main 类
bootJar {
    mainClass = 'com.example.order.api.OrderApiApplication'
}

order-service 和 order-dao 类似,只依赖自己需要的模块。order-common 最干净,甚至不需要 Spring Boot 插件。

30.5 构建 Docker 镜像

微服务最终要跑在容器里。Gradle 官方推荐用 com.google.cloud.tools.jib 插件,它不需要 Docker 守护进程,直接生成镜像。我个人觉得比 Dockerfile 方便,尤其是 CI 环境里。

// 在 order-api/build.gradle 里添加
plugins {
    id 'com.google.cloud.tools.jib' version '3.4.0'
}

jib {
    from {
        image = 'eclipse-temurin:17-jre-alpine'
    }
    to {
        image = "registry.example.com/order-api:${project.version}"
        // 如果私有仓库需要认证,可以配置 auth
    }
    container {
        mainClass = 'com.example.order.api.OrderApiApplication'
        ports = ['8080']
    }
}

执行 ./gradlew :order-api:jib,镜像就构建并推送到了仓库。嗯,这里要注意,第一次跑会下载基础镜像,有点慢,但后面就快了。

30.6 CI/CD 流水线配置

咱们用 GitHub Actions 做演示。流水线分三步:编译、测试、构建镜像并推送。我曾经见过有人把测试和构建写在一个 job 里,结果测试失败镜像也推上去了,那叫一个乱。

# .github/workflows/ci-cd.yml
name: Order Platform CI/CD

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Build and Test
        run: ./gradlew build
      - name: Build Docker Image
        run: ./gradlew :order-api:jib
        env:
          DOCKER_REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
          DOCKER_REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }}

避坑指南:千万不要把镜像仓库的密码写在代码里。用 GitHub Secrets 或者环境变量注入。我曾经有一次不小心把密码提交到了公开仓库,十分钟后收到一堆机器人登录警告……从那以后,我养成了每次提交前检查敏感信息的习惯。

30.7 多模块构建的常见问题

最后,我总结几个实战中容易踩的坑:

  • 依赖版本冲突:子模块引用了不同版本的传递依赖。解决办法是在根项目用 dependencyManagement 统一声明版本。
  • 构建缓存失效:如果你改了公共模块的代码,但其他模块没重新编译。试试 --rerun-tasks 或者清理 .gradle 缓存。
  • Docker 镜像层缓存:Jib 默认会利用层缓存,但如果你的依赖经常变,可以考虑把 jib.container.creationTime 设为 USE_TIMESTAMPS 来避免不必要的层重建。

好了,这一章的内容就到这里。你跟着把项目搭起来,跑一遍流水线,应该就能体会到 Gradle 在微服务构建中的威力。说白了,工具是死的,流程是活的,多动手才能变成自己的经验。


公众号:蓝海资料掘金营,微信deep3321