All Projects → csieflyman → multi-projects-architecture-with-Ktor

csieflyman / multi-projects-architecture-with-Ktor

Licence: MIT license
A Ktor real world example built on multi-projects architecture

Programming Languages

kotlin
9241 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to multi-projects-architecture-with-Ktor

flame
Ruby web-framework
Stars: ✭ 43 (+48.28%)
Mutual labels:  backend, web-framework
tinyhttp
🦕 Deno port of tinyhttp, 0-legacy, tiny & fast web framework
Stars: ✭ 84 (+189.66%)
Mutual labels:  backend, web-framework
mojo.js
🦄 The Mojolicious real-time web framework for Node.js
Stars: ✭ 145 (+400%)
Mutual labels:  backend, web-framework
Elixir Phoenix Realworld Example App
Exemplary real world application built with Elixir + Phoenix
Stars: ✭ 764 (+2534.48%)
Mutual labels:  backend, realworld
laravel-realworld-example-app
Exemplary RealWorld backend API built with Laravel PHP framework.
Stars: ✭ 34 (+17.24%)
Mutual labels:  backend, realworld
Cakephp Realworld Example App
Stars: ✭ 103 (+255.17%)
Mutual labels:  backend, realworld
mangooio
An Intuitive, Lightweight, High Performance Full Stack Java Web Framework.
Stars: ✭ 52 (+79.31%)
Mutual labels:  backend, web-framework
Goyave
🍐 Elegant Golang REST API Framework
Stars: ✭ 811 (+2696.55%)
Mutual labels:  backend, web-framework
Laravel Realworld Example App
Exemplary real world backend API built with Laravel
Stars: ✭ 954 (+3189.66%)
Mutual labels:  backend, realworld
Express Bookshelf Realworld Example App
🐳 An Express and Bookshelf based backend implementation of the RealWorld API Spec.
Stars: ✭ 45 (+55.17%)
Mutual labels:  backend, realworld
MultiplatformPlayground
Kotlin Multiplatform project in Jetpack Compose & SwiftUI with shared ViewModel layer and File upload
Stars: ✭ 72 (+148.28%)
Mutual labels:  backend, ktor
nestjs-config
NestJS Module for Nonfig services. Nonfig combines Configurations and Features. So you change features, and release swiftly, and measure to digital impact.
Stars: ✭ 40 (+37.93%)
Mutual labels:  backend
rust-realworld-example-app
Example Rust codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld API spec.
Stars: ✭ 19 (-34.48%)
Mutual labels:  realworld
woliveiras.github.io
My personal Blog
Stars: ✭ 25 (-13.79%)
Mutual labels:  backend
nuxt-realworld
🛠 Built a Example App of RealWorld with Nuxt & Composition API ⚗️
Stars: ✭ 127 (+337.93%)
Mutual labels:  realworld
fastapi-framework
A FastAPI Framework for things like Database, Redis, Logging, JWT Authentication, Rate Limits and Sessions
Stars: ✭ 26 (-10.34%)
Mutual labels:  backend
issue-wanted
🏷 Web application to help beginners to start contributing into Haskell projects
Stars: ✭ 61 (+110.34%)
Mutual labels:  backend
node-fastcgi
Create FastCGI applications with node.js
Stars: ✭ 49 (+68.97%)
Mutual labels:  backend
skinny-micro
🎤 Micro Web framework to build Servlet applications in Scala, the core part of Skinny Framework 2
Stars: ✭ 57 (+96.55%)
Mutual labels:  web-framework
lucene
Apache Lucene open-source search software
Stars: ✭ 1,009 (+3379.31%)
Mutual labels:  backend

Multi-Projects Architecture with Ktor

Multi-Projects Architecture

This project has two subprojects built on common infrastructure and library but each subproject can be deployed optionally as a ktor module . Subproject has its own

  • user types and roles
  • authentication methods
  • notification event types
  • openapi document
    application {
        modules = [
            fanpoll.infra.ApplicationKt.main,
            fanpoll.ops.OpsProjectKt.opsMain,
            fanpoll.club.ClubProjectKt.clubMain
        ]
    }

Technique Stack

  • Kotlin 1.5.21
  • Ktor 1.6.2
  • Gradle 7.1.1
  • PostgreSQL 13.2
  • Redis 6.2.1
  • Kotlinx Serialization, Kotlinx Coroutine
  • Exposed ORM, HikariCP, Flyway database migration tool
  • Koin DI

Ktor Enhancement

  • Ktor Feature (Plugin)
    • integrate Koin DI for initializing ktor feature
    • feature configurations can be configured with DSL or external config file
  • i18n Support
    • specify the language supported by application in config file. e.g. app.infra.i18n.langs = ["zh-TW", "en"]
    • support HOCON and Java Properties message source
    • retrieving supported languages from an HTTP request cookie and Accept-Language header by ktor ApplicationCall extension function lang()
  • OpenAPI Generator
    • generate openapi document respectively for each subproject
    • support HTTP basic authentication to protect your API document
    • integrate with gradle git plugin to add git version information into API document
  • Authentication and Role-Based Authoriation, like Spring Security
    • You can specify authentication providers, user types and roles in ktor route DSL function
authorize(ClubAuth.Admin) {

    put<UUIDEntityIdLocation, UpdateUserForm, Unit>(ClubOpenApi.UpdateUser) { _, form ->
        clubUserService.updateUser(form)
        call.respond(HttpStatusCode.OK)
    }
}
  • Type-safe and Validatable Configuration
    • Instead of using ktor ApplicationConfig, I use Config4k to convert HOCON config file to Kotlin data class. Moreover, data class can override validate() function to validate the configuration values
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
    • use Konform to validate incoming request body and path, query parameters automatically before pass it to the route dsl function. I will support JSR-303 annotation in the future

Infrastructure

  • Logging
    • use coroutine channel to write log asynchronously to different destinations, including file, database, AWS Kinesis stream
    • log types include request, error, user login and notification
  • Authentication Methods
    • Service: API key authentication
    • User: password authentication with bcrypt
  • Redis
  • Notification Service
    • use coroutine channel to send notifications asynchronously to multiple notification channels, includeing Email(AWS SES), Push(Firebase) and SMS(not implemented yet)
    • integrate freemarker template engine
    • send i18n messages vary by user language preference
  • Mobile App Management
    • support multiple apps
    • check app version whether need to upgrade or not
    • manage user devices and push tokens
  • Performance Tunning
    • coroutine channel and Java ExeuctorService threadpool parameters can be configured with the config file. You can create parameterized config files for different environments in the deploy folder

繁體中文 (Traditional Chinese)

Ktor 是 JetBrains 開發的 Web 框架,其特性是

  • 100% 純 Kotlin 開發
  • 使用 Coroutine 非同步的方式處理請求
  • Unopinionated 使得 Ktor 核心更加地輕量、啟動快速

其中 Unopinionated 的特性,Ktor 並沒有預先整合常用的第三方框架及函式庫,例如 Dependency Injection, ORM, Logging...等,而是讓開發者能自由選擇整合至 Ktor。但也因為如此,開發者不僅需要花時間進行整合的工作,對於新手或是習慣 Spring Boot 全家桶的開發者,更是存在不小的門檻。

另一方面,Ktor 的架構設計是讓開發者透過實作 Feature (官方未來將改名為 Plugin),把 intercepting function 加入至 request pipeline 之中,藉此增加功能或改變行為。這種方式與 Spring Boot 規定開發者實作特定的介面或繼承特定類別,再透過 dependency injection 的方式注入有所不同,再加上如果開發者不熟 Lambda 與 DSL 寫法,就不知該如何動手實作 Feature。

由於 Ktor 是一個很年輕的框架,所以使用人數不多,網路上的範例也很少,而且大多為展示簡單的單一功能,缺乏將各個功能整合起來的完整後端服務範例。另一方面,Ktor 本身是 unopinionated 的微框架,官方文件也沒有建議的開發指南,所以開發者必須根據自身經驗,事先思考如何規劃專案的檔案結構及程式架構,才能建構容易維護的專案。

綜合以上原因,本專案使用以下技術且包含常見網站後端服務功能的範例,供大家參考學習。截至 2021-07-21,codebase 累計已有 241 個 kt 檔,不含空白行超過 13000 行

Technique Stack

  • Kotlin 1.5.21
  • Ktor 1.6.2
  • Gradle 7.1.1
  • PostgreSQL 13.2
  • Redis 6.2.1
  • Kotlinx Serialization, Kotlinx Coroutine
  • Exposed ORM, HikariCP, Flyway database migration tool
  • Koin DI

Ktor Enhancement

Ktor 是一個微框架,缺乏與常用第三方框架或函式庫之間的整合,例如 DI, ORM,甚至連 request data validation 及 i18n 也沒有實作。所以如果要直接使用 Ktor 開發專案,就無法像 Spring Boot 設定後立即使用,必須要先花時間自行開發缺少的功能及整合,所以本專案對 Ktor 進行了以下增強改善

  • Ktor Feature (官方未來將改名為 Plugin)
    • 整合了 Koin DI 至 Ktor Feature, 協助初始化 Feature
    • Ktor Feature 的開發慣例是使用 DSL 語法進行設定,但實務上,許多參數設定必須由外部設定檔或環境變數提供。所以本專案實作的所有 Feature 都支援以上2種方式,並且以外部設定檔為優先
  • i18n Support
    • 在設定檔指定系統支援的語言 app.infra.i18n.langs = ["zh-TW", "en"]
    • 多國語言訊息檔支援 HOCON 及 Java Properties 2 種格式
    • 可從 cookie 或 Accept-Language header 取得 HTTP 請求的語言,再使用 Ktor ApplicationCall 的 extension function lang() 進行操作
  • OpenAPI Generator
    • 自行實作文件產生器,所以可根據自身需求調整以最佳化產出的文件
    • 以編程方式撰寫 OpenAPI Definition,並且集中在專屬的 kt 檔案方便管理,取代一般在 route function 加註大量 annotation 的方式,使得 route function 看起來更簡潔
    • 每個子專案可各自產生文件,避免將所有不同功能的 API 都集中在一份文件
    • 支援 Http Basic Authentication,保護 API 文件不外流
    • 整合 Gradle Git Plugin,將 Git 版本資訊、建置部署時間…等資訊加進文件中
  • Authentication and Role-Based Authoriation
    • 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

Multi-Projects Architecture

近年微服務架構興起,對於規模較小的開發團隊而言,一開始就規劃拆分為多個微服務是個沉重的負擔,所以大多還是從單體式架構 monolithic 出發,往後再視情況逐步拆分為微服務。雖然這種開發方式廣泛被採用,但實際上並不是所有團隊都能無痛轉換至微服務架構,這取決於單體式架構裡的各個功能實作上是否低耦合,甚至模組化。 另一方面,即使往後將各個功能拆分成微服務,因為通常拆分的時間點不一, 或是由不同的工程師負責實作,如果沒有事先規劃及規定統一的作法,就容易導致各個微服務在基礎設施功能方面的實作方式上有所差異,提高了往後開發維護及維運的成本。

本專案雖然是單體式架構,但是以模組化開發方式為準則,並且在共同的函式庫及基礎設施功能上建立子專案。規劃子專案可以遵循 DDD 的概念, 將子專案對應到一個 Bounded Context。為了確保各個 Context 獨立不耦合,在架構設計上,每個子專案可以定義自己的使用者及角色,還有事件通知。另外在實作方面,每個子專案都是一個 Ktor Module,所以可以透過調整設定檔決定要部署那些模組。如果一個模組沒有被部署,則不會進行任何初始化動作,也就不會部署任何 API,這與一般 Gradle multi-projects 只在專案結構方面模組化建置的作法相比,可以更進一步節省執行期的運行資源。

    application {
        modules = [
            fanpoll.infra.ApplicationKt.main,
            fanpoll.ops.OpsProjectKt.opsMain,
            fanpoll.club.ClubProjectKt.clubMain
        ]
    }

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
  • 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 參數都可以透過設定檔進行調整。我們可以事先在 deploy 資料夾下建立各種環境的設定檔,根據每個環境的效能需求及限制給予不同的設定值

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: 登入、登出、變更密碼

Build & Deploy

Build Steps

  1. 設定 git branch 對應至部署環境
    在 build.gradle.kts 檔案找到以下設定,你也可自行增加 stage, test…等環境對應。Gradle shadow plugin 會根據當下的 git branch 打包程式及 deploy/config/${env} 資料夾裡的檔案,最後壓縮為 zip 檔
    val devBranchName = "dev"
    val releaseBranchName = "main"
    val branchToEnvMap: Map<String, String> = mapOf(devBranchName to "dev", releaseBranchName to "prod")
  1. 製作部署環境設定檔
    根據 git branch 對應的環境 env 變數值
    建立deploy/config/${env}/application-${env}.conf檔案。
    設定檔範例可參考 deploy/config/dev/application-dev.conf
  2. 執行 gradle shadowEnvDistZip 打包為 zip

Deploy Steps

  1. 根據 application.conf 設定執行所需要的環境變數
  2. 解壓縮 zip 檔(內含部署腳本檔案)
  3. 設定 configs.sh
  4. 執行 start.sh 啟動服務
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].