[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: 登入、登出、变更密码

<<:  .NET Core第10天_搭配EF Core串联资料库Db First_使用EntityFramework执行检视的MVC控制器

>>:  Day 5:Hello....android world! 建立第一个KMM专案(Android)

用python下载东西

其实不用安装requests就可以下载东西 python的urllib.request.urlope...

[Day20] Esp32用AP mode + AHT10 - (程序码讲解)

1.前言 首先,祝各位中秋佳节愉快~(明天又要继续上班上课了),不知道各位小夥伴连假期间是否在某些方...

如何避免Overfitting

Overfitting是在执行任何模型的时候我们都要注意的问题,今天就来聊聊overfitting是...

用排队上厕所来比喻Python Thread的Lock机制!

Python MultiThread多线程中的Lock用途? Lock机制通常会使用於,当有多个线程...

Day 15 使用renderHook

来,今天来看renderHooks这个library吧,大家可以看到昨天的写法,透过render c...