👋

Ktorを紹介するとともに、2024/4現在の注意点も紹介

2024/04/15に公開

前提とするバージョン

io.ktor:ktor-server-core-jvm:2.3.9
io.ktor:ktor-server-netty-jvm:2.3.9
IntelliJ IDEA 2024.1

Ktorの特徴

  • クエリパラメーターなどを、@Resourceという機能を使って、タイプセーフなルーティングで実装できる
  • ルーティングとプラグインと組み合わせやすく、ネストが深い特定のルーティングだけに認証機能などのプラグインを適用するといったことが自然にできる
  • swaggerドキュメントの自動生成ができる 割と目玉機能
  • 設定ファイルとして使いやすいHOCON(Human-Optimized Config Object Notation)を使った設定ができる

タイプセーフルーティング(@Resource)などの実装例

実装例です。ポイントは、以下の通りです。

  • リソースの定義(URLの定義)する箇所には、リソースの定義(URLの定義)だけを書く。
  • リソースハンドラー(Spring Frameworkっぽいネーミングですが)のファイルには、リソースに対するハンドラーを書く。
  • 基本的に全部、全部タイプセーフな@Resourceという機能を使って実装する
// Routing.kt
// ルーティングのプラグインを適用する
fun Application.configureRouting() {
    install(Resources)
    routing {
        pingRoute()
        route("v1") {
            authenticatedRoute()
        }
    }
}
// AuthenticatedRoute.kt
// リソースハンドラーを並べる
fun Route.authenticatedRoute() {
    companiesResourceHandler()
    hoge1ResourceHandler()
    hoge2ResourceHandler()
    hoge3ResourceHandler()
}
// CompaniesResource.kt
// URLの定義を書きます
class CompaniesResource {
    @Resource("companies")
    class Index(
        val q: String? = null,
    )

    @Resource("companies/{id}")
    class Id(
        val id: Int,
    )
}
/**
CompaniesResourceHandler.kt
ここでは~Resourceに対するハンドラーを書きます
以下、routingのメソッドは使わない
io.ktor.server.routing.get
io.ktor.server.routing.post
io.ktor.server.routing.put
io.ktor.server.routing.delete

以下、resourcesパッケージのメソッドを使う
io.ktor.server.resources.get
io.ktor.server.resources.post
io.ktor.server.resources.put
io.ktor.server.resources.delete
 */
fun Route.companiesResourceHandler() {
    val companiesController by inject<companiesController>() // DI with ktor-koin 
    get<CompaniesResource.Index> { params ->
        val responseBody = companiesController.index(call.principal(), params)
        call.respond(status = HttpStatusCode.OK, message = responseBody)
    }
    post<CompaniesResource.Id> { params ->
        val createBody = call.receive<CompaniesCreateBody>()
        val responseBody = companiesController.create(call.principal(), createBody)
        call.respond(status = HttpStatusCode.OK, message = responseBody)
    }
}
// schema.kt
// リクエストボディを書きます
data class CompaniesCreateBody(
    val name: String,
)

swaggerドキュメントの自動生成での不具合や注意点

生成されるswaggerドキュメントのopenapiのバージョンが新しくなった

IntelliJ IDEA2024.1でswaggerドキュメントの最初の1行目のopenapiのバージョンの箇所が変わりました。

- openapi: "3.0.3"
+ openapi: "3.1.0"

しかし、3.1.0は、OpenAPIGeneratorがまだ不具合を起こす(arrayで且つ同じ型が連続して定義されると正しい型にならない)ため、自動生成された後、手でopenapi: "3.0.3"へ戻す等の対応を今はするようにしています。

https://twitter.com/momosetkn/status/1777653138004017505

Nullableにしていないプリミティブ型がrequiredにならない

2024/1頃に、IntelliJ IDEA2024.1で修正される予定とのことでしたが、まだ修正されていません。

issue報告 https://youtrack.jetbrains.com/issue/IDEA-324930/OpenAPI-generator-should-add-required-properties-for-non-nullable-fields?s=OpenAPI-generator-should-add-required-properties-for-non-nullable-fields

enumのフィールド毎にenumの定義を生成してしまい、重複する

以下のようにdata classを定義した場合、

data class ExampleClass(
    val enum1: ExampleEnum,
    val enum2: ExampleEnum,
    val enum3: ExampleEnum,
)

enum class ExampleEnum {
    A, B, C
}

以下のように重複して生成されます

    ExampleClass:
      type: "object"
      properties:
        enum1:
          type: "string"
          enum:
          - "A"
          - "B"
          - "C"
        enum2:
          type: "string"
          enum:
          - "A"
          - "B"
          - "C"
        enum3:
          type: "string"
          enum:
          - "A"
          - "B"
          - "C"
      required:
      - "enum1"
      - "enum2"
      - "enum3"

issue報告 https://youtrack.jetbrains.com/issue/IDEA-344875/OpenAPI-generator-enum-definitions-are-duplicated

isはじまりのBooleanのフィールドがスキップされる

代わりに、flgという名前のフィールドを使うと、正しく生成されます。

issue報告 https://youtrack.jetbrains.com/issue/IDEA-332548/Boolean-fields-with-is-prefix-in-names-are-skipped-during-OpenApi-scheme-generation-with-Ktor-IDEA-plugin

auto-reloadの不具合

Ktorのauto-reload時にpluginエラーがあると応答しなくなり、原因究明ができない問題のワークアラウンド

GitHubで編集を提案

Discussion