Skip to content

Kotlin、Ktor、Exposed とデータベースを統合する

コード例: tutorial-server-db-integration

使用されるプラグイン:

Routing
ルーティングは、サーバーアプリケーションにおける受信リクエストを処理するためのコアプラグインです。
Static Content
スタイルシート、スクリプト、画像などの静的コンテンツを提供する方法を学習します。
Content Negotiation
ContentNegotiation プラグインには、クライアントとサーバー間のメディアタイプをネゴシエートすることと、特定の形式でコンテンツをシリアライズ/デシリアライズすることという、主に2つの目的があります。
Status pages
%plugin_name% を使用すると、Ktor アプリケーションはスローされた例外やステータスコードに基づいて、あらゆる失敗状態に適切に応答できます。
kotlinx.serialization
ContentNegotiation プラグインには、クライアントとサーバー間のメディアタイプをネゴシエートすることと、特定の形式でコンテンツをシリアライズ/デシリアライズすることという、主に2つの目的があります。
ExposedPostgres

この記事では、Kotlin 用の SQL ライブラリである Exposed を使用して、Ktor サービスをリレーショナルデータベースと統合する方法を学習します。

このチュートリアルを終えるまでに、以下の方法を学習します。

  • JSON シリアライズを使用する RESTful サービスを作成する。
  • これらのサービスに異なるリポジトリを注入する。
  • 偽装(フェイク)リポジトリを使用してサービスのユニットテストを作成する。
  • Exposed と依存性注入(DI)を使用して動作するリポジトリを構築する。
  • 外部データベースにアクセスするサービスをデプロイする。

以前のチュートリアルでは、Task Manager の例を使用して、

リクエストの処理
ルーティング、リクエスト、パラメーターの基本を Kotlin と Ktor でタスクマネージャーアプリケーションを構築することで学習します。
RESTful API の作成
Kotlin と Ktor を使用してバックエンドサービスを構築する方法を、JSON ファイルを生成する RESTful API の例を特徴として学習します。
、または
Thymeleaf テンプレートでの Web アプリの構築
Kotlin と Ktor、Thymeleaf テンプレートを使用して Web サイトを構築する方法を学習します。
などの基本を扱いました。 これらのチュートリアルは、シンプルなインメモリの TaskRepository を使用したフロントエンド機能に焦点を当てていましたが、 このガイドでは、Ktor サービスが Exposed SQL ライブラリ を介してリレーショナルデータベースと対話する方法を示すことに焦点を移します。

このガイドは長く複雑ですが、それでもすぐに動作するコードを作成し、新しい機能を段階的に導入できます。

このガイドは2つのパートに分かれます。

  1. インメモリリポジトリでアプリケーションを作成する。
  2. インメモリリポジトリを PostgreSQL を使用するものに切り替える。

前提条件

このチュートリアルは独立して行うことができますが、Content Negotiation と REST に慣れるために、

RESTful API の作成
Kotlin と Ktor を使用してバックエンドサービスを構築する方法を、JSON ファイルを生成する RESTful API の例を特徴として学習します。
チュートリアルを完了することをお勧めします。

IntelliJ IDEA のインストールをお勧めしますが、お好みの他の IDE を使用することもできます。

RESTful サービスとインメモリリポジトリを作成する

まず、Task Manager RESTful サービスを再作成します。最初はインメモリリポジトリを使用しますが、最小限の労力で置き換えられるような設計を構築します。

これには6つの段階があります。

  1. 初期プロジェクトを作成する。
  2. スターターコードを追加する。
  3. CRUD ルートを追加する。
  4. シングルページアプリケーション(SPA)を追加する。
  5. アプリケーションを手動でテストする。
  6. 自動テストを追加する。

プラグインを使用して新しいプロジェクトを作成する

Ktor Project Generator を使用して新しいプロジェクトを作成するには、以下の手順に従ってください。

  1. Ktor Project Generator に移動します。

  2. [Project artifact] フィールドに、プロジェクトアーティファクト名として com.example.ktor-exposed-task-app を入力します。 Ktor Project Generator でプロジェクトアーティファクトに名前を付ける

  3. プラグインセクションで、[Add] ボタンをクリックして以下のプラグインを検索し、追加します。

    1. Routing
    2. Content Negotiation
    3. Kotlinx.serialization
    4. Static Content
    5. Status Pages
    6. Exposed
    7. Postgres

    Ktor Project Generator でプラグインを追加する

  4. プラグインを追加したら、プラグインセクションの右上にある 7 plugins ボタンをクリックして、追加されたプラグインを確認します。

    プロジェクトに追加されるすべてのプラグインのリストが表示されます。 Ktor Project Generator のプラグインドロップダウン

  5. [Download] ボタンをクリックして、Ktor プロジェクトを生成およびダウンロードします。

  6. 生成されたプロジェクトを IntelliJ IDEA またはお好みの他の IDE で開きます。

  7. src/main/kotlin/com/example に移動し、CitySchema.kt および UsersSchema.kt ファイルを削除します。

  8. Databases.kt ファイルを開き、configureDatabases() 関数のコンテンツを削除します。

    kotlin

    この機能を削除する理由は、Ktor Project Generator がユーザーと都市に関するデータを HSQLDB または PostgreSQL に永続化する方法を示すサンプルコードを追加しているためです。このチュートリアルでは、そのサンプルコードは必要ありません。

スターターコードを追加する

  1. src/main/kotlin/com/example に移動し、model というサブパッケージを作成します。
  2. model パッケージ内に、新しい Task.kt ファイルを作成します。
  3. Task.kt を開き、優先度を表す enum とタスクを表す class を追加します。

    kotlin

    Task クラスには、

    kotlinx.serialization
    ContentNegotiation プラグインには、クライアントとサーバー間のメディアタイプをネゴシエートすることと、特定の形式でコンテンツをシリアライズ/デシリアライズすることという、主に2つの目的があります。
    ライブラリの Serializable 型がアノテーションされています。

    以前のチュートリアルと同様に、インメモリリポジトリを作成します。ただし、今回はリポジトリが interface を実装するようにすることで、後で簡単に置き換えられるようにします。

  4. model サブパッケージ内に、新しい TaskRepository.kt ファイルを作成します。
  5. TaskRepository.kt を開き、以下の interface を追加します。

    kotlin
  6. 同じディレクトリ内に新しい FakeTaskRepository.kt ファイルを作成します。
  7. FakeTaskRepository.kt を開き、以下の class を追加します。

    kotlin

ルートを追加する

  1. src/main/kotlin/com/example にある Serialization.kt ファイルを開きます。
  2. 既存の Application.configureSerialization() 関数を以下の実装に置き換えます。

    kotlin

    これは、

    RESTful API の作成
    Kotlin と Ktor を使用してバックエンドサービスを構築する方法を、JSON ファイルを生成する RESTful API の例を特徴として学習します。
    チュートリアルで実装したルーティングと同じですが、今回はリポジトリをパラメーターとして routing() 関数に渡しています。パラメーターの型が interface であるため、さまざまな実装を注入できます。

    configureSerialization() にパラメーターを追加したため、既存の呼び出しはコンパイルされなくなります。幸いなことに、この関数は一度しか呼び出されません。

  3. src/main/kotlin/com/example 内の Application.kt ファイルを開きます。
  4. module() 関数を以下の実装に置き換えます。

    kotlin

    FakeTaskRepository のインスタンスを configureSerialization() に注入しています。長期的な目標は、これを PostgresTaskRepository に置き換えられるようにすることです。

クライアントページを追加する

  1. src/main/resources/static にある index.html ファイルを開きます。
  2. 現在のコンテンツを以下の実装に置き換えます。

    html

    これは以前のチュートリアルで使用された SPA と同じものです。JavaScript で書かれており、ブラウザ内で利用可能なライブラリのみを使用するため、クライアント側の依存関係を心配する必要はありません。

アプリケーションを手動でテストする

    この最初のイテレーションでは、データベースに接続する代わりにインメモリリポジトリを使用しているため、アプリケーションが適切に構成されていることを確認する必要があります。

  1. src/main/resources/application.yaml に移動し、postgres 設定を削除します。

    yaml
  2. IntelliJ IDEA で、実行ボタン (IntelliJ IDEA の実行アイコン) をクリックしてアプリケーションを起動します。

  3. ブラウザで http://0.0.0.0:8080/static/index.html に移動します。3つのフォームとフィルタリングされた結果を表示するテーブルで構成されるクライアントページが表示されるはずです。

    タスクマネージャー クライアントを表示するブラウザウィンドウ
  4. [Go] ボタンを使用してフォームに入力して送信し、アプリケーションをテストします。テーブルの項目にある [View] および [Delete] ボタンを使用します。

    タスクマネージャー クライアントを表示するブラウザウィンドウ

自動ユニットテストを追加する

  1. src/test/kotlin/com/example にある ApplicationTest.kt を開き、以下のテストを追加します。

    kotlin

    これらのテストをコンパイルして実行するには、Ktor クライアント用の Content Negotiation プラグインへの依存関係を追加する必要があります。

  2. gradle/libs.versions.toml ファイルを開き、以下のライブラリを指定します。

    kotlin
  3. build.gradle.kts を開き、以下の依存関係を追加します。

    kotlin
  4. IntelliJ IDEA で、エディターの右側にある通知 Gradle アイコン (IntelliJ IDEA の Gradle アイコン) をクリックして Gradle の変更をロードします。

  5. IntelliJ IDEA で、テストクラス定義の隣にある実行ボタン (IntelliJ IDEA の実行アイコン) をクリックしてテストを実行します。

    その後、[Run] ペインでテストが正常に実行されたことが確認できます。

    IntelliJ IDEA の [Run] ペインに表示される成功したテスト結果

PostgreSQL リポジトリを追加する

インメモリデータを使用する動作中のアプリケーションができたので、次のステップはデータストレージを PostgreSQL データベースに外部化することです。

これを行うには、以下の手順に従います。

  1. PostgreSQL 内にデータベーススキーマを作成する。
  2. 非同期アクセス用に TaskRepository を適合させる。
  3. アプリケーション内でデータベース接続を構成する。
  4. Task 型を関連するデータベーステーブルにマップする。
  5. このマッピングに基づいて新しいリポジトリを作成する。
  6. 起動コードでこの新しいリポジトリに切り替える。

データベーススキーマを作成する

  1. お好みのデータベース管理ツールを使用して、PostgreSQL 内に新しいデータベースを作成します。 名前は覚えていれば何でも構いません。この例では、 ktor_tutorial_db を使用します。

    TIP

    PostgreSQL の詳細については、公式ドキュメント を参照してください。

    IntelliJ IDEA では、データベースツールを使用して PostgreSQL データベースに接続し、管理する ことができます。

  2. データベースに対して以下の SQL コマンドを実行します。これらのコマンドはデータベーススキーマを作成し、データを投入します。

    sql

    以下に注意してください。

    • task という単一のテーブルを作成しており、namedescription、および priority の列があります。これらは Task クラスのプロパティにマップする必要があります。
    • テーブルが既に存在する場合は再作成されるため、スクリプトを繰り返し実行できます。
    • SERIAL 型の id という追加の列があります。これは整数値で、各行に主キーを付与するために使用されます。これらの値はデータベースによって自動的に割り当てられます。

既存のリポジトリを適合させる

    データベースに対してクエリを実行する際、HTTP リクエストを処理するスレッドのブロックを避けるために、非同期で実行することが望ましいです。Kotlin では、これは コルーチン を介して最もよく管理されます。

  1. src/main/kotlin/com/example/model にある TaskRepository.kt ファイルを開きます。

  2. すべてのインターフェースメソッドに suspend キーワードを追加します。

    kotlin

    これにより、インターフェースメソッドの実装は、異なるコルーチンディスパッチャーで作業を開始できるようになります。

    これで、FakeTaskRepository のメソッドを一致させる必要がありますが、その実装ではディスパッチャーを切り替える必要はありません。

  3. FakeTaskRepository.kt ファイルを開き、すべてのメソッドに suspend キーワードを追加します。

    kotlin

    ここまでは、新しい機能は何も導入していません。その代わりに、データベースに対して非同期でクエリを実行する PostgresTaskRepository を作成するための基盤を築きました。

データベース接続を構成する

    このチュートリアルの最初のパート で、Databases.kt 内にある configureDatabases() メソッドのサンプルコードを削除しました。これで独自の 実装を追加する準備ができました。

  1. src/main/kotlin/com/example にある Databases.kt ファイルを開きます。
  2. Database.connect() 関数を使用してデータベースに接続し、設定値を環境に合わせて調整します。

    kotlin

    url には以下のコンポーネントが含まれていることに注意してください。

    • localhost:5432 は PostgreSQL データベースが実行されているホストとポートです。
    • ktor_tutorial_db はサービス実行時に作成されるデータベースの名前です。

    TIP

    詳細については、 Exposed でのデータベースとデータソースの操作 を参照してください。

オブジェクト/リレーショナルマッピングを作成する

  1. src/main/kotlin/com/example に移動し、db という新しいパッケージを作成します。
  2. db パッケージ内に、新しい mapping.kt ファイルを作成します。
  3. mapping.kt を開き、TaskTable および TaskDAO 型を追加します。

    kotlin

    これらの型は Exposed ライブラリを使用して、Task 型のプロパティをデータベースの task テーブルの列にマップします。TaskTable 型は基本的なマッピングを定義し、TaskDAO 型はタスクの作成、検索、更新、削除を行うヘルパーメソッドを追加します。

    DAO 型のサポートは Ktor Project Generator によって追加されていないため、Gradle ビルドファイルに関連する依存関係を追加する必要があります。

  4. gradle/libs.versions.toml ファイルを開き、以下のライブラリを指定します。

    kotlin
  5. build.gradle.kts ファイルを開き、以下の依存関係を追加します。

    kotlin
  6. IntelliJ IDEA で、エディターの右側にある通知 Gradle アイコン (IntelliJ IDEA の Gradle アイコン) をクリックして Gradle の変更をロードします。

  7. mapping.kt ファイルに戻り、以下の2つのヘルパー関数を追加します。

    kotlin

    suspendTransaction() はコードブロックを受け取り、IO Dispatcher を介してデータベーストランザクション内で実行します。これは、ブロッキング作業をスレッドプールにオフロードするように設計されています。

    daoToModel()TaskDAO 型のインスタンスを Task オブジェクトに変換します。

  8. 以下の不足しているインポートを追加します。

    kotlin

新しいリポジトリを作成する

    これで、データベース固有のリポジトリを作成するために必要なすべてのリソースが揃いました。

  1. src/main/kotlin/com/example/model に移動し、新しい PostgresTaskRepository.kt ファイルを作成します。
  2. PostgresTaskRepository.kt ファイルを開き、以下の実装で新しい型を作成します。

    kotlin

    この実装では、TaskDAO および TaskTable 型のヘルパーメソッドを使用してデータベースと対話します。この新しいリポジトリを作成したので、残りのタスクはルート内でこれを使用するように切り替えることだけです。

新しいリポジトリに切り替える

    外部データベースに切り替えるには、リポジトリの型を変更するだけです。

  1. src/main/kotlin/com/example にある Application.kt ファイルを開きます。
  2. Application.module() 関数で、FakeTaskRepositoryPostgresTaskRepository に置き換えます。

    kotlin

    インターフェースを介して依存関係を注入しているため、実装の切り替えはルートを管理するコードからは透過的です。

  3. IntelliJ IDEA で、再実行ボタン (IntelliJ IDEA の再実行アイコン) をクリックしてアプリケーションを再起動します。

  4. http://0.0.0.0:8080/static/index.html に移動します。 UI は変更されませんが、データはデータベースからフェッチされるようになりました。
  5. これを検証するには、フォームを使用して新しいタスクを追加し、PostgreSQL のタスクテーブルに保持されているデータをクエリします。

    TIP

    IntelliJ IDEA では、 Query ConsoleSELECT SQL ステートメントを使用してテーブルデータをクエリできます。

    SQL

    クエリを実行すると、新しいタスクを含むデータが Service ペインに表示されるはずです。

    IntelliJ IDEA の [Service] ペインに表示されるタスクのテーブル

これで、データベースをアプリケーションに統合する作業が正常に完了しました。

FakeTaskRepository 型は本番コードではもはや必要ないため、 src/test/com/example のテストソースセットに移動できます。

最終的なプロジェクト構造は次のようになります。

IntelliJ IDEA の [Project] ビューに表示される src フォルダー

次のステップ

これで、Ktor RESTful サービスと通信するアプリケーションができました。これは Exposed で記述されたリポジトリを使用して PostgreSQL にアクセスします。また、Web サーバーやデータベースを必要とせずに、コア機能を検証する一連のテストも備わっています。

この構造は、必要に応じて任意の機能をサポートするために拡張できますが、まずはフォールトトレランス、セキュリティ、スケーラビリティなどの非機能的な側面を検討することをお勧めします。データベース設定を構成ファイルに抽出する ことから始めることができます。