[Day 3] 以 Ktor Module 实作模组化开发

Ktor Module

Ktor Module 可以用来组织程序码,本身仅是一个 Application 类别的 extension function,让 server 启动时执行而已。Ktor 并没有规定 Module 内部应该如何实作,也没有限制 Module 的颗粒度要多大,官方文件只提到 module 可以是某些 plugin 及 routes 的集合。例如我可以实作一个 Shutdown module,其 extension function 的内部实作是安装 Ktor ShutdownUrl plugin,藉此增加1个 GET route,让外界呼叫此 GET API 後能停止 server。

Multi-Project with Ktor Module

实作上,我把每个子专案当作是一个 Ktor module,而且都依赖於 infrastrcture module。infrastrcture module 负责安装 Ktor Plugin,提供底层的函式库及功能。我建立了2个子专案 ops 及 club,透过设定 ktor.application.modules 属性值,Ktor 会在启动时依序执行这3个 module 的 extension fuction。设定时要注意 infra module 必须要放在第一个位置。

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

其中 infra module 的 extension function 内部实作是负责初始化 Ktor plugin 及 ProjectManager

fun Application.main() {
    install(LoggingFeature)
    install(DatabaseFeature)
    install(RedisFeature)
    install(Authentication)
    // ...以下省略
    koin {
        modules(
            module(createdAtStart = true) {
                single { ProjectManager(get()) }
            }
        )
    }
    // ...以下省略
}

club module 的 extension function 内部实作是负责初始化 club 专案。第一步是先透过 Koin DI 取得 ProjectManager 物件,然後载入 club 专案设定档,最後再建立 club Project 物件并注册至 ProjectManager

每个子专案可各自定义以下项目,infra module 再根据 project 物件执行相对应的功能

  • 使用者类型及其角色
  • 验证 API 请求的方式
  • 讯息通知类型
  • OpenAPI 文件
fun Application.clubMain() {
    val projectManager = get<ProjectManager>()
    val projectConfig = ProjectManager.loadConfig<ClubConfig>(ClubConst.projectId)
    projectManager.register(
        Project(
            ClubConst.projectId,
            projectConfig.auth.principalSourceAuthConfigs,
            ClubUserType.values().map { it.value },
            ClubOpenApi.Instance,
            ClubNotification.AllTypes
        )
    )
    // ...以下省略
}

class Project(
    override val id: String,
    val principalSourceAuthConfigs: List<PrincipalSourceAuthConfig>,
    val userTypes: List<UserType>,
    val projectOpenApi: ProjectOpenApi,
    val notificationTypes: List<NotificationType>? = null
) : IdentifiableObject<String>()

今天从程序开发层面说明如何使用 Ktor Module 进行模组化开发,明天将会从建置部署层面切入,说明如何使用 Gradle Multi-Project Builds 建置专案、还有使用 Gradle Shadow Plugin 及 Docker Compose 打包部署。


<<:  Day8 - 程序设计报价 (三) - 常见问题

>>:  灵异现象 - 我是你的恶梦

Day 09:今天又想不出标题了!tmux plugin 和 mouse mode

我把从第一天到现在每天的 Home 目录都放上 GitHub 了,README.md 里面有说明 ...

Day4 - 建立Android模拟器

身为一个没有Android手机的用户 当要测试程序时,又借不到Android手机,怎麽办呢?? An...

Rust-特徵(Trait)(一)

什麽是特徵 根据官网的解释就是 特徵会告诉编译器特定型别与其他型别共享的功能。可以使用特徵定义来抽象...

[Day30] Tableau 轻松学 - 总结

前言 Tableau 轻松学 系列文章也已经到了尾声,从带着读者从头认识 Tableau 并且调查人...

Day 25 -资料库应用小程序 创建资料库

从我们上一篇设计出的实体关系模型 ( Entity - Relationship Model, E-...