iT邦幫忙

2021 iThome 鐵人賽

DAY 5
0
Modern Web

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

[Day 5] Ktor 微框架就如同一間毛胚屋,先來列出想要整合的框架及實作的功能清單

Ktor 的架構設計及開發風格是我所喜歡的,但相對地使用 Ktor 開發也要付出代價。因為 Ktor 以 unopinionated 的原則進行設計,所以很多功能不像 Spring 框架開箱即用,必須要先花時間自行開發缺少的功能及整合其它函式庫,這使得許多開發者猶豫是否要使用 Ktor 開發。另一方面,Ktor 是一個很年輕的框架,使用人數不多,網路上的範例也很少,而且大多為展示簡單的單一功能,缺乏將各個功能整合起來的完整後端服務範例,所以開發者必須根據自身經驗,事先思考如何規劃專案的檔案結構及程式架構,才能建構大型且容易維護的專案。

雖然 Ktor 不像 Spring 是一間什麼都有能馬上入住的樣品屋,但卻是一間小而美的毛胚屋,我可以按照我的想法進行內部隔間及裝潢,符合我的客製化需求,最後打造一間擁有個人風格的房屋。

以下是我想要整合的框架及實作的功能清單,最後完成一個後端服務範例,供大家參考學習。截至目前為止,codebase 累計已有 241 個 kt 檔,不含空白行超過 13000 行。後續我會逐一說明如何實作,讀者可直接挑有興趣的主題閱讀。

Technique Stack

  • Kotlin 1.5.30
  • Ktor 1.6.3
  • Gradle 7.2
  • PostgreSQL 13.4
  • Redis 6.2.1
  • Kotlinx Serialization, Kotlinx Coroutine
  • Koin DI
  • Exposed ORM, HikariCP, Flyway database migration tool

Ktor Enhancement

  • Ktor Plugin
    • Ktor Plugin 的開發慣例是使用 DSL 語法進行設定,但實務上,許多參數設定必須由外部設定檔或環境變數提供。所以我實作的所有 Plugin 都支援以上2種方式,並且以外部設定檔為優先
  • i18n
    • 在設定檔指定系統支援的語言 app.infra.i18n.langs = ["zh-TW", "en"]
    • 多國語言訊息檔支援 HOCON 及 Java Properties 2 種格式
    • 可從 cookie 或 Accept-Language header 取得 HTTP 請求的語言,再使用 Ktor ApplicationCall 的 extension function lang() 進行操作
  • OpenAPI Document Generator
    • 自行實作文件產生器,最佳化自動產出 API 文件
    • 以編程方式撰寫 OpenAPI Definition,並且集中在專屬的 kt 檔案方便管理,讓 route function 看起來更簡潔
    • 每個子專案可各自產生文件,避免將所有不同功能的 API 都集中在一份文件
    • 支援 Http Basic Authentication,保護 API 文件不外流
    • 整合 Gradle Git Plugin,將 Git 版本資訊、建置部署時間…等資訊加進文件中
  • Authentication and Role-Based Authoriation (類似 Spring Security)
    • Ktor 本身僅實作 authentication 機制,並沒有定義使用者及其角色。我允許每個子專案定義自己的使用者及其角色,並且整合至原有的 Ktor authentication 機制,達到類似 Spring Security 的功能
      authorize(ClubAuth.Admin) {
          put<UUIDEntityIdLocation, UpdateUserForm, Unit>(ClubOpenApi.UpdateUser) { _, form ->
              clubUserService.updateUser(form)
              call.respond(HttpStatusCode.OK)
          }
      }
      
  • Type-safe and Validatable Configuration
    • Ktor 讀取設定檔的方式是透過 ApplicationConfig 物件,但只能使用 getString() 函式取值。我使用 Config4k 將設定值轉換至 Kotlin data class,不僅可以達到 type-safe 的效果,直接操作物件的寫法也更簡潔易懂。除此之外,我也在 config4k 轉換時插入驗證函式 validate(),類別可實作此函式檢查設定值是否合法
      data class SessionConfig(
          val expireDuration: Duration? = null,
          val extendDuration: Duration? = null
      ) : ValidateableConfig {
      
          override fun validate() {
              require(if (expireDuration != null && extendDuration != null) expireDuration > extendDuration else true) {
                  "expireDuration $expireDuration should be greater than extendDuration $extendDuration"
              }
          }
      }
      
  • Request Data Validation
    • Ktor 沒有實作對請求資料進行驗證的功能,我透過自定義 route extension fuction 的方式,先將 request body, path parameter, query parameter 轉為 data class 之後,隨即進行資料驗證,最後再傳入 route DSL function 作為參數進行操作。目前我使用 Konform,以 type-safe DSL 的方式撰寫驗證邏輯,未來再考慮是否支援 JSR-303 annotation

Infrastructure

  • Logging
    • 使用 coroutine channel 非同步地寫入 log 至檔案、資料庫或 AWS Kinesis stream
    • 目前包含 request log, error log, login log, notification log,每一種 log 都可以各自設定寫入的目的地
  • Authentication Methods
    • Service 驗證: 支援 API key authentication 及信任 ip 白名單
    • User 驗證: 使用 bcrypt password authentication。預計未來支援 OAuth
  • Redis
    • 儲存 user session,並且支援 Redis PubSub Keyspace Notification 實作 session key 逾期通知機制
    • 支援 data cache
    • 目前使用 ktorio redis client,特色是實作簡單而且是基於 coroutines。不過這是 JetBrains ktor 團隊的實驗性專案,所以未來預計將轉換為 Lettuce coroutine extension
  • Notification Service
    • 使用 coroutine channel 非同步地發送通知至多個管道,包含 email(AWS SES), push(Firebase), sms(尚未串接)
    • 整合 freemarker template engine 處理 email 內容
    • 可根據使用者偏好語言發送通知
  • Mobile App Management
    • 支援管理多個 app
    • 驗證客戶端 app 版本,檢查是否有新版本,甚至強迫升級
    • 管理使用者裝置的推播 token
  • Performance Tunning
    • 所有的 Coroutine Channel 及 Java ExeuctorService threadpool 參數都可以透過設定檔進行調整,我們可以根據每個環境的效能需求及限制給予不同的設定值。

Multi-Project

每個子專案各自擁有以下項目

  • 使用者類型及其角色
  • 驗證 API 請求的方式
  • 事件通知
  • OpenAPI 文件

Ops Project

為了整合 DevOps 流程,我內建 Ops 子專案,目前包含 Operation Team 及 AppTeam 2種使用者角色,另外還有 Root 及 Monitor 2種服務角色。每種角色只可以呼叫有其權限的 API,可以進行權限控管。

  • Root: 管理 Ops 專案的使用者
  • Monitor: 實作類似 spring-actuator 的監控功能,目前支援 healthCheck,預計未來將提供更多系統狀態的資訊
  • Operation Team:
    • 可以填寫任意文字作為 email 的內容,傳送給符合查詢條件的使用者
    • 給定查詢條件將資料庫裡符合的資料匯出成 Excel 檔案,再寄送至指定的 email
  • App Team: App 版本管理
  • User: 登入、登出、變更密碼

Club Project

Club 為展示功能的範例專案, 目前包含 Admin 及 Member 2種使用者角色,以 iOS, Android App 作為使用者客戶端

  • Admin
    • 管理 Club 使用者
    • 可以填寫任意文字作為 email 及推播通知的內容,傳送給符合查詢條件的使用者
  • Member: 目前沒有 Member 才能執行的功能
  • User: 登入、登出、變更密碼

上一篇
[Day 4] 使用 Gradle Multi-Project Builds X Shadow Plugin X Docker Compose 建置、打包、部署
下一篇
[Day 6] 使用 kotlinx.serialization 轉換 JSON
系列文
基於 Kotlin Ktor 建構支援模組化開發的 Web 框架30

尚未有邦友留言

立即登入留言