Kotlin、Ktor、Exposed とデータベースを統合する
コード例: tutorial-server-db-integration
使用されるプラグイン:
この記事では、Kotlin 用の SQL ライブラリである Exposed を使用して、Ktor サービスをリレーショナルデータベースと統合する方法を学習します。
このチュートリアルを終えるまでに、以下の方法を学習します。
- JSON シリアライズを使用する RESTful サービスを作成する。
- これらのサービスに異なるリポジトリを注入する。
- 偽装(フェイク)リポジトリを使用してサービスのユニットテストを作成する。
- Exposed と依存性注入(DI)を使用して動作するリポジトリを構築する。
- 外部データベースにアクセスするサービスをデプロイする。
以前のチュートリアルでは、Task Manager の例を使用して、
TaskRepository
を使用したフロントエンド機能に焦点を当てていましたが、 このガイドでは、Ktor サービスが Exposed SQL ライブラリ を介してリレーショナルデータベースと対話する方法を示すことに焦点を移します。 このガイドは長く複雑ですが、それでもすぐに動作するコードを作成し、新しい機能を段階的に導入できます。
このガイドは2つのパートに分かれます。
前提条件
このチュートリアルは独立して行うことができますが、Content Negotiation と REST に慣れるために、
IntelliJ IDEA のインストールをお勧めしますが、お好みの他の IDE を使用することもできます。
RESTful サービスとインメモリリポジトリを作成する
まず、Task Manager RESTful サービスを再作成します。最初はインメモリリポジトリを使用しますが、最小限の労力で置き換えられるような設計を構築します。
これには6つの段階があります。
プラグインを使用して新しいプロジェクトを作成する
Ktor Project Generator を使用して新しいプロジェクトを作成するには、以下の手順に従ってください。
Ktor Project Generator に移動します。
[Project artifact] フィールドに、プロジェクトアーティファクト名として com.example.ktor-exposed-task-app を入力します。
プラグインセクションで、[Add] ボタンをクリックして以下のプラグインを検索し、追加します。
- Routing
- Content Negotiation
- Kotlinx.serialization
- Static Content
- Status Pages
- Exposed
- Postgres
プラグインを追加したら、プラグインセクションの右上にある 7 plugins ボタンをクリックして、追加されたプラグインを確認します。
プロジェクトに追加されるすべてのプラグインのリストが表示されます。
[Download] ボタンをクリックして、Ktor プロジェクトを生成およびダウンロードします。
生成されたプロジェクトを IntelliJ IDEA またはお好みの他の IDE で開きます。
src/main/kotlin/com/example に移動し、CitySchema.kt および UsersSchema.kt ファイルを削除します。
Databases.kt ファイルを開き、
configureDatabases()
関数のコンテンツを削除します。kotlinこの機能を削除する理由は、Ktor Project Generator がユーザーと都市に関するデータを HSQLDB または PostgreSQL に永続化する方法を示すサンプルコードを追加しているためです。このチュートリアルでは、そのサンプルコードは必要ありません。
スターターコードを追加する
- src/main/kotlin/com/example に移動し、model というサブパッケージを作成します。
- model パッケージ内に、新しい Task.kt ファイルを作成します。
Task.kt を開き、優先度を表す
enum
とタスクを表すclass
を追加します。kotlinTask
クラスには、kotlinx.serializationライブラリのContentNegotiation プラグインには、クライアントとサーバー間のメディアタイプをネゴシエートすることと、特定の形式でコンテンツをシリアライズ/デシリアライズすることという、主に2つの目的があります。Serializable
型がアノテーションされています。以前のチュートリアルと同様に、インメモリリポジトリを作成します。ただし、今回はリポジトリが
interface
を実装するようにすることで、後で簡単に置き換えられるようにします。- model サブパッケージ内に、新しい TaskRepository.kt ファイルを作成します。
TaskRepository.kt を開き、以下の
interface
を追加します。kotlin- 同じディレクトリ内に新しい FakeTaskRepository.kt ファイルを作成します。
FakeTaskRepository.kt を開き、以下の
class
を追加します。kotlin
ルートを追加する
- src/main/kotlin/com/example にある Serialization.kt ファイルを開きます。
既存の
Application.configureSerialization()
関数を以下の実装に置き換えます。kotlinこれは、
RESTful API の作成チュートリアルで実装したルーティングと同じですが、今回はリポジトリをパラメーターとしてKotlin と Ktor を使用してバックエンドサービスを構築する方法を、JSON ファイルを生成する RESTful API の例を特徴として学習します。routing()
関数に渡しています。パラメーターの型がinterface
であるため、さまざまな実装を注入できます。configureSerialization()
にパラメーターを追加したため、既存の呼び出しはコンパイルされなくなります。幸いなことに、この関数は一度しか呼び出されません。- src/main/kotlin/com/example 内の Application.kt ファイルを開きます。
module()
関数を以下の実装に置き換えます。kotlinFakeTaskRepository
のインスタンスをconfigureSerialization()
に注入しています。長期的な目標は、これをPostgresTaskRepository
に置き換えられるようにすることです。
クライアントページを追加する
- src/main/resources/static にある index.html ファイルを開きます。
現在のコンテンツを以下の実装に置き換えます。
htmlこれは以前のチュートリアルで使用された SPA と同じものです。JavaScript で書かれており、ブラウザ内で利用可能なライブラリのみを使用するため、クライアント側の依存関係を心配する必要はありません。
アプリケーションを手動でテストする
src/main/resources/application.yaml に移動し、
postgres
設定を削除します。yamlIntelliJ IDEA で、実行ボタン (
) をクリックしてアプリケーションを起動します。
ブラウザで http://0.0.0.0:8080/static/index.html に移動します。3つのフォームとフィルタリングされた結果を表示するテーブルで構成されるクライアントページが表示されるはずです。
[Go] ボタンを使用してフォームに入力して送信し、アプリケーションをテストします。テーブルの項目にある [View] および [Delete] ボタンを使用します。
この最初のイテレーションでは、データベースに接続する代わりにインメモリリポジトリを使用しているため、アプリケーションが適切に構成されていることを確認する必要があります。
自動ユニットテストを追加する
src/test/kotlin/com/example にある ApplicationTest.kt を開き、以下のテストを追加します。
kotlinこれらのテストをコンパイルして実行するには、Ktor クライアント用の Content Negotiation プラグインへの依存関係を追加する必要があります。
gradle/libs.versions.toml ファイルを開き、以下のライブラリを指定します。
kotlinbuild.gradle.kts を開き、以下の依存関係を追加します。
kotlinIntelliJ IDEA で、エディターの右側にある通知 Gradle アイコン (
) をクリックして Gradle の変更をロードします。
IntelliJ IDEA で、テストクラス定義の隣にある実行ボタン (
) をクリックしてテストを実行します。
その後、[Run] ペインでテストが正常に実行されたことが確認できます。
PostgreSQL リポジトリを追加する
インメモリデータを使用する動作中のアプリケーションができたので、次のステップはデータストレージを PostgreSQL データベースに外部化することです。
これを行うには、以下の手順に従います。
- PostgreSQL 内にデータベーススキーマを作成する。
- 非同期アクセス用に
TaskRepository
を適合させる。 - アプリケーション内でデータベース接続を構成する。
Task
型を関連するデータベーステーブルにマップする。- このマッピングに基づいて新しいリポジトリを作成する。
- 起動コードでこの新しいリポジトリに切り替える。
データベーススキーマを作成する
お好みのデータベース管理ツールを使用して、PostgreSQL 内に新しいデータベースを作成します。 名前は覚えていれば何でも構いません。この例では、 ktor_tutorial_db を使用します。
TIP
PostgreSQL の詳細については、公式ドキュメント を参照してください。
IntelliJ IDEA では、データベースツールを使用して PostgreSQL データベースに接続し、管理する ことができます。
データベースに対して以下の SQL コマンドを実行します。これらのコマンドはデータベーススキーマを作成し、データを投入します。
sql以下に注意してください。
- task という単一のテーブルを作成しており、name、 description、および priority の列があります。これらは
Task
クラスのプロパティにマップする必要があります。 - テーブルが既に存在する場合は再作成されるため、スクリプトを繰り返し実行できます。
SERIAL
型の id という追加の列があります。これは整数値で、各行に主キーを付与するために使用されます。これらの値はデータベースによって自動的に割り当てられます。
- task という単一のテーブルを作成しており、name、 description、および priority の列があります。これらは
既存のリポジトリを適合させる
src/main/kotlin/com/example/model にある TaskRepository.kt ファイルを開きます。
すべてのインターフェースメソッドに
suspend
キーワードを追加します。kotlinこれにより、インターフェースメソッドの実装は、異なるコルーチンディスパッチャーで作業を開始できるようになります。
これで、
FakeTaskRepository
のメソッドを一致させる必要がありますが、その実装ではディスパッチャーを切り替える必要はありません。FakeTaskRepository.kt ファイルを開き、すべてのメソッドに
suspend
キーワードを追加します。kotlinここまでは、新しい機能は何も導入していません。その代わりに、データベースに対して非同期でクエリを実行する
PostgresTaskRepository
を作成するための基盤を築きました。
データベースに対してクエリを実行する際、HTTP リクエストを処理するスレッドのブロックを避けるために、非同期で実行することが望ましいです。Kotlin では、これは コルーチン を介して最もよく管理されます。
データベース接続を構成する
- src/main/kotlin/com/example にある Databases.kt ファイルを開きます。
Database.connect()
関数を使用してデータベースに接続し、設定値を環境に合わせて調整します。kotlinurl
には以下のコンポーネントが含まれていることに注意してください。localhost:5432
は PostgreSQL データベースが実行されているホストとポートです。ktor_tutorial_db
はサービス実行時に作成されるデータベースの名前です。
TIP
詳細については、 Exposed でのデータベースとデータソースの操作 を参照してください。
このチュートリアルの最初のパート で、Databases.kt 内にある configureDatabases()
メソッドのサンプルコードを削除しました。これで独自の 実装を追加する準備ができました。
オブジェクト/リレーショナルマッピングを作成する
- src/main/kotlin/com/example に移動し、db という新しいパッケージを作成します。
- db パッケージ内に、新しい mapping.kt ファイルを作成します。
mapping.kt を開き、
TaskTable
およびTaskDAO
型を追加します。kotlinこれらの型は Exposed ライブラリを使用して、
Task
型のプロパティをデータベースの task テーブルの列にマップします。TaskTable
型は基本的なマッピングを定義し、TaskDAO
型はタスクの作成、検索、更新、削除を行うヘルパーメソッドを追加します。DAO 型のサポートは Ktor Project Generator によって追加されていないため、Gradle ビルドファイルに関連する依存関係を追加する必要があります。
gradle/libs.versions.toml ファイルを開き、以下のライブラリを指定します。
kotlinbuild.gradle.kts ファイルを開き、以下の依存関係を追加します。
kotlinIntelliJ IDEA で、エディターの右側にある通知 Gradle アイコン (
) をクリックして Gradle の変更をロードします。
mapping.kt ファイルに戻り、以下の2つのヘルパー関数を追加します。
kotlinsuspendTransaction()
はコードブロックを受け取り、IO Dispatcher を介してデータベーストランザクション内で実行します。これは、ブロッキング作業をスレッドプールにオフロードするように設計されています。daoToModel()
はTaskDAO
型のインスタンスをTask
オブジェクトに変換します。以下の不足しているインポートを追加します。
kotlin
新しいリポジトリを作成する
- src/main/kotlin/com/example/model に移動し、新しい PostgresTaskRepository.kt ファイルを作成します。
PostgresTaskRepository.kt ファイルを開き、以下の実装で新しい型を作成します。
kotlinこの実装では、
TaskDAO
およびTaskTable
型のヘルパーメソッドを使用してデータベースと対話します。この新しいリポジトリを作成したので、残りのタスクはルート内でこれを使用するように切り替えることだけです。
これで、データベース固有のリポジトリを作成するために必要なすべてのリソースが揃いました。
新しいリポジトリに切り替える
- src/main/kotlin/com/example にある Application.kt ファイルを開きます。
Application.module()
関数で、FakeTaskRepository
をPostgresTaskRepository
に置き換えます。kotlinインターフェースを介して依存関係を注入しているため、実装の切り替えはルートを管理するコードからは透過的です。
IntelliJ IDEA で、再実行ボタン (
) をクリックしてアプリケーションを再起動します。
- http://0.0.0.0:8080/static/index.html に移動します。 UI は変更されませんが、データはデータベースからフェッチされるようになりました。
これを検証するには、フォームを使用して新しいタスクを追加し、PostgreSQL のタスクテーブルに保持されているデータをクエリします。
TIP
IntelliJ IDEA では、 Query Console と
SELECT
SQL ステートメントを使用してテーブルデータをクエリできます。SQLクエリを実行すると、新しいタスクを含むデータが Service ペインに表示されるはずです。
外部データベースに切り替えるには、リポジトリの型を変更するだけです。
これで、データベースをアプリケーションに統合する作業が正常に完了しました。
FakeTaskRepository
型は本番コードではもはや必要ないため、 src/test/com/example のテストソースセットに移動できます。
最終的なプロジェクト構造は次のようになります。
![IntelliJ IDEA の [Project] ビューに表示される src フォルダー](/ktor/tutorial_server_db_integration_src_folder.png)
次のステップ
これで、Ktor RESTful サービスと通信するアプリケーションができました。これは Exposed で記述されたリポジトリを使用して PostgreSQL にアクセスします。また、Web サーバーやデータベースを必要とせずに、コア機能を検証する一連のテストも備わっています。
この構造は、必要に応じて任意の機能をサポートするために拡張できますが、まずはフォールトトレランス、セキュリティ、スケーラビリティなどの非機能的な側面を検討することをお勧めします。データベース設定を構成ファイルに抽出する ことから始めることができます。