[Day 4] 使用 Gradle Multi-Project Builds X Shadow Plugin X Docker Compose 建置、打包、部署

以往 Gradle 只能使用 Groovy 语言撰写 Script,因为我对 Groovy 不熟,所以大多从 Google 寻找到解法後,再复制贴上稍作修改解决。不过既然现在 Gradle 已经支援 Kotlin DSL,当然就要自己写 gradle task,整合多个 plugin 使出连续技,释放 Gradle 强大功能了。

Gradle Multi-Project Builds 建置

我把专案结构拆分为4个子专案,并参考官方文件 Gradle multi-project builds 进行设定。以下只会撷取重要的程序码进行说明,如果读者想进一步了解细节,可以参考 github 完整程序码

// settings.gradle.kts
rootProject.name = "fanpoll"
include("app", "infra", "projects:ops", "projects:club")

buildSrc 则是把多个专案的共用逻辑集中写为 convention plugin。

因为专案需要 kotlin serialization compiler plugin 及 shadow plugin,所以必须要在 buildSrc/build.gradle.kts 的 repositories 加上 gradlePluginPortal() 才能下载 plugin

plugins {
    `kotlin-dsl`
}

repositories {
    // Use the plugin portal to apply community plugins in convention plugins.
    gradlePluginPortal()
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30")
    implementation("org.jetbrains.kotlin:kotlin-serialization:1.5.30")
    implementation("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0")
}

Shadow Plugin 打包

为了避免 shadow plugin 在打包 ops, club 子专案的时候,把 infra 专案的 transitive dependencies 的 jar 档也打包进去,造成 fat jar 的大小爆增,所以我在 subproject-conventions 设定 shadowJar task 的 configuration 的 isCanBeResolved 为 ture,再设定 transitive 属性为 false。

plugins {
    id("app.java-conventions")
}

dependencies {
    implementation(project(":infra"))
}

configurations.api.get().isCanBeResolved = true

tasks.shadowJar {
    configurations = listOf(project.configurations.api.get().setTransitive(false))
    dependencies {
        exclude(dependency("org.jetbrains.kotlin:kotlin-stdlib:.*"))
        exclude(dependency("org.jetbrains.kotlin:kotlin-stdlib-jdk8:.*"))
    }
}

最後在 app/build.gradle.kts 设定 project dependenies,而且要设定 configuration = "shadow",shadow plugin 才会把这些子专案合并为1个 fat jar 档

dependencies {
    implementation(project(":infra", configuration = "shadow"))
    implementation(project(":projects:ops", configuration = "shadow"))
    implementation(project(":projects:club", configuration = "shadow"))
}

除了程序码之外,设定档…等其它档案也要一起打包。但是设定档必须要考虑到不同部署环境的设定值可能会不同,例如 dev, staging, prod 的效能参数会根据硬体规格调整。如果你的 git branch 与部署环境存在着对应关系的话,那麽可以写 gradle task 搭配 Git Plugin 自动依据当下的 git branch 打包对应的设定档或其它档案

  1. 设定 git branch 与 deployment env 之间的 mapping,例如 feature/xxx 都对应到 dev 环境
  2. 执行 gradle installShadowDist task 之前,先透过 Git Plugin 拿到目前所在的 git branch,再 copy 对应的 env 设定档至 src/dist 目录
  3. shadow plugin 打包 src/dist 目录下的档案至 fat jar

Docker Compose 部署

虽然 Shadow Plugin 已经打包成1个 jar 档,直接下 java -jar 指令就可以执行了,不过因为本专案有搭配 PostgreSQL 与 Redis,所以用 Docker Compose 搞定一切会比较方便。

因为有使用 shadow,所以要注意 Dockerfile 里面的 build 目录要改为 build/install/app-shadow/

FROM openjdk:11-jre-slim
EXPOSE 8080:8080
RUN mkdir /app
COPY ./build/install/app-shadow/ /app/
WORKDIR /app/bin
CMD ["./app"]

至於 docker-compose.yml 就不贴上来了,请读者自行到 github 查看

如果想要对 Docker image 及 container 做更多细节的客制化操作,可以使用 Gradle Docker Plugin

最後推荐一下 IntelliJ IDEA 的 docker plugin,在本地端开发时,就能透过 IDE 查看及操作 docker container 非常方便,而且最新版 2021.2 版本又进一步优化,有更多 docker 参数可以设定。

这两天已经说明了如何实作多专案模组化的开发、建置、打包、部署,明天会开始进入 Ktor 的世界,整合第三方框架及函式库,还有实作 Ktor 缺少的套件来强化 Ktor。


<<:  第一次的爬虫(2)

>>:  [Angular] Day9. Transforming Data Using Pipes

WhatsApp Business 商业帐号的独特功能

WhatsApp是世界上最多人使用的即时通讯软件,每月有20亿活跃用户,用户透过WhatsApp每天...

课堂笔记 - 深度学习 Deep Learning (12)

Logistic regression的介绍 Logistic regression就跟其他的回归...

Day 8 - DOM - Element Object

Element Object 所有的 HTML Elements 都继承了 Element Obje...

[Day01] 前言:常见的前端实战技能有哪些?

Credit: https://lilly021.com/angular-vs-react-vs-...

Day20 资料冗余和Partition

接下来谈谈资料冗余的策略 最简单最好管理的冗余就是完完全全的复制一份在别的地方,就是我们经常说的备份...