iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Modern Web

基於 Kotlin Ktor 建構支援模組化開發的 Web 框架系列 第 4

[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。


上一篇
[Day 3] 以 Ktor Module 實作模組化開發
下一篇
[Day 5] Ktor 微框架就如同一間毛胚屋,先來列出想要整合的框架及實作的功能清單
系列文
基於 Kotlin Ktor 建構支援模組化開發的 Web 框架30

尚未有邦友留言

立即登入留言