Kotlin RPC の最初のステップ
コード例: tutorial-kotlin-rpc-app
使用されているプラグイン:
Kotlin RPC (Remote Procedure Call) は、Kotlinエコシステムに加わった新しく魅力的な機能で、確立された基盤に基づいて構築されており、kotlinx.rpc
ライブラリ上で動作します。
kotlinx.rpc
ライブラリを使用すると、通常のKotlin言語構成のみを使用してネットワーク境界を越えてプロシージャ呼び出しを行うことができます。そのため、REST と Google RPC (gRPC) の両方に対する代替手段を提供します。
この記事では、Kotlin RPC のコアコンセプトを紹介し、シンプルなアプリケーションを構築します。その後、ご自身のプロジェクトでこのライブラリを評価することができます。
前提条件
このチュートリアルでは、Kotlin プログラミングの基本的な理解があることを前提としています。Kotlin を初めて使用する場合は、いくつかの入門資料を確認することをお勧めします。
最高の体験を得るために、統合開発環境 (IDE) として IntelliJ IDEA Ultimate の使用をお勧めします。これは、生産性を向上させる包括的なサポートとツールを提供するためです。
RPC とは何か?
ローカルプロシージャ呼び出し vs. リモートプロシージャ呼び出し
プログラミング経験がある人なら誰でも、プロシージャ呼び出しの概念に精通しているでしょう。これはあらゆるプログラミング言語における基本的な概念です。技術的には、これらは常に同じプログラム内で実行されるため、ローカルプロシージャ呼び出しです。
リモートプロシージャ呼び出しとは、関数呼び出しとパラメータが何らかの形でネットワークを介して転送され、別のVM/実行可能ファイル内で実装が行われる場合を指します。戻り値は、呼び出しが行われたマシンへ逆の経路をたどって戻ります。
呼び出しが行われたマシンをクライアント、実装が置かれているマシンをサーバーと考えるのが最も簡単です。しかし、必ずしもそうである必要はありません。RPC呼び出しは、ピアアーキテクチャの一部として両方向で発生する可能性があります。ただし、話を単純にするために、クライアント/サーバー展開を想定しましょう。
RPCフレームワークの基本
どのRPCフレームワークも、特定の基本要素を提供しなければなりません。これらは、従来のITインフラストラクチャ内でリモートプロシージャ呼び出しを実装する際に不可欠です。用語は様々であり、責任分担も異なる場合がありますが、すべてのRPCフレームワークは以下を提供する必要があります。
- リモートで呼び出されるプロシージャを宣言する方法。オブジェクト指向プログラミングでは、インターフェースが論理的な選択肢です。これは、現在の言語が提供するインターフェース構造であるか、W3Cで使われているWeb IDLのような言語に依存しない標準である可能性があります。
- パラメータと戻り値に使用される型を指定する手段。ここでも、言語に依存しない標準を使用できます。しかし、現在の言語で標準のデータ型宣言にアノテーションを付ける方が簡単な場合もあります。
- ヘルパークラス( )は、プロシージャ呼び出しをネットワーク経由で送信できる形式に変換し、結果の戻り値をアンパックするために使用されます。これらのスタブは、コンパイルプロセス中または実行時に動的に作成できます。
- 基盤となる がヘルパークラスを管理し、リモートプロシージャ呼び出しのライフサイクルを監督します。サーバー側では、このランタイムは継続的にリクエストを処理できるように、何らかのサーバーに組み込む必要があります。
- 呼び出されるプロシージャを表現し、送信されるデータをシリアライズし、ネットワーク上で情報を変換するためのプロトコルを選択(または定義)する必要があります。過去には、ゼロから新しいプロトコルを定義した技術(CORBAのIIOP)もあれば、再利用に焦点を当てた技術(SOAPのHTTP POST)もありました。
マーシャリング vs. シリアライゼーション
RPCフレームワークでは、「 」と「 」という言葉を使います。これは、ネットワーク経由で送信される情報をパックおよびアンパックするプロセスです。これはシリアライゼーションのスーパーセットと考えることができます。マーシャリングでは、オブジェクトをシリアライズしますが、呼び出されるプロシージャと、その呼び出しが行われたコンテキストに関する情報もパッケージ化する必要があります。
RPCのコアコンセプトを導入したところで、サンプルアプリケーションを構築してkotlinx.rpc
でどのように適用されるかを見てみましょう。
Hello, kotlinx.rpc
ネットワーク経由でピザを注文するアプリケーションを作成してみましょう。コードをできるだけシンプルに保つため、コンソールベースのクライアントを使用します。
プロジェクトを作成する
まず、クライアントとサーバーの両方の実装を含むプロジェクトを作成します。
より複雑なアプリケーションでは、クライアントとサーバーで個別のモジュールを使用するのがベストプラクティスです。ただし、このチュートリアルでは簡略化のため、両方で単一のモジュールを使用します。
- IntelliJ IDEA を起動します。
ようこそ画面で、New Project をクリックします。
または、メインメニューから File | New | Project を選択します。
- Name フィールドに、プロジェクト名として KotlinRpcPizzaApp を入力します。
- 残りのデフォルト設定はそのままにして、Create をクリックします。
通常、すぐにプロジェクトビルドファイルを構成するでしょう。しかし、それは技術の理解を深めるものではない実装の詳細であるため、そのステップには最後に立ち戻ります。
共有型を追加する
あらゆるRPCプロジェクトの核となるのは、リモートで呼び出されるプロシージャを定義するインターフェースと、それらのプロシージャの定義に使用される型です。
マルチモジュールプロジェクトでは、これらの型を共有する必要があります。しかし、この例では、このステップは不要です。
クライアントを実装する
- src/main/kotlin に移動し、新しい Client.kt ファイルを作成します。
- Client.kt を開き、次の実装を追加します。 kotlin
RPC呼び出しの準備と実行には、わずか25行のコードしか必要ありません。明らかに多くのことが行われているため、コードをセクションに分けて見ていきましょう。
kotlinx.rpc
ライブラリは、クライアント側でランタイムをホストするために
kotlinx.rpc
を簡単に統合できるようになります。 KtorクライアントとKotlin RPCはどちらもコルーチンを中心に構築されているため、runBlocking
を使用して最初のコルーチンを作成し、その中でクライアントの残りの部分を実行します。
TIP
runBlocking
は、本番コードではなく、スパイクやテストのために設計されていることに注意してください。 次に、Ktorクライアントのインスタンスを標準的な方法で作成します。kotlinx.rpc
は、情報の転送に内部的に
installKrpc()
関数を使用して、それが読み込まれていることを確認するだけで十分です。 このKtorクライアントを作成したら、次にリモートプロシージャを呼び出すためのKtorRpcClient
オブジェクトを作成します。サーバーの場所と情報の転送に使用されるメカニズムを設定する必要があります。
この時点で、標準的なセットアップが完了し、問題領域に特化した機能を使用する準備が整いました。クライアントを使用して、PizzaShop
インターフェースのメソッドを実装するクライアントプロキシオブジェクトを作成できます。
その後、リモートプロシージャ呼び出しを行い、その結果を使用できます。
この時点で、非常に多くの作業が自動的に行われていることに注意してください。呼び出しの詳細とすべてのパラメータはメッセージに変換され、ネットワーク経由で送信され、その後、戻り値が受信されてデコードされます。この透過的な処理が、初期設定の成果です。
最後に、通常通りクライアントをシャットダウンする必要があります。
サーバーを実装する
サーバー側の実装は2つの部分に分かれます。まず、インターフェースの実装を作成する必要があり、次に、それをサーバー内でホストする必要があります。
- src/main/kotlin に移動し、新しい Server.kt ファイルを作成します。
- Server.kt を開き、次のインターフェースを追加します。 kotlin
これは現実世界の実装ではありませんが、デモを動かすには十分です。
実装の2番目の部分はKtorに基づいています。
同じファイルに次のコードを追加します。
kotlin内訳は次のとおりです。
まず、構成に使用される指定された拡張関数を使用して、Ktor/Nettyのインスタンスを作成します。
kotlin次に、Ktor Application型を拡張するセットアップ関数を宣言します。これにより、
kotlinx.rpc
プラグインがインストールされ、1つ以上のルートが宣言されます。kotlinルーティングセクション内では、Ktor Routing DSLに対する
kotlinx.rpc
拡張機能を使用してエンドポイントを宣言します。クライアント側と同様に、URLを指定してシリアライゼーションを設定します。ただし、このケースでは、私たちの実装はそのURLで受信リクエストをリッスンします。kotlinregisterService
を使用して、インターフェースの実装をRPCランタイムに提供することに注意してください。単一のインスタンスよりも多くが必要になる場合もありますが、それは後続の記事のトピックです。
依存関係を追加する
アプリケーションを実行するために必要なコードはすべて揃いましたが、現時点ではコンパイルすらできず、実行には程遠い状態です。kotlinx.rpcプラグインと共にKtor Project Generatorを使用するか、ビルドファイルを manually で構成することもできます。これもそれほど複雑ではありません。
- build.gradle.kts ファイルに、次のプラグインを追加します。 kotlin
Kotlinプラグインの理由は明らかです。その他について説明します。
kotlinx.serialization
プラグインは、KotlinオブジェクトをJSONに変換するためのヘルパー型を生成するために必要です。kotlinx.serialization
がリフレクションを使用しないことを覚えておいてください。- Ktorプラグインは、アプリケーションとそのすべての依存関係をバンドルするfat JARをビルドするために使用されます。
- RPCプラグインは、クライアント側のスタブを生成するために必要です。
- 次の依存関係を追加します。 kotlin
これにより、Ktorクライアントとサーバー、
kotlinx.rpc
ランタイムのクライアント側とサーバー側の部分、およびkotlinx.rpc
とkotlinx-serialization
を統合するためのライブラリが追加されます。これにより、プロジェクトを実行してRPC呼び出しを開始できるようになります。
例を拡張する
最後に、将来の開発のための強固な基盤を確立するために、サンプルアプリケーションの複雑さを向上させましょう。
- PizzaShop.kt ファイルで、クライアントIDを含むように
orderPizza
メソッドを拡張し、指定されたクライアントの保留中のすべての注文を返すviewOrders
メソッドを追加します。kotlinList
やSet
ではなくFlow
を返すことで、コルーチンライブラリの利点を活用できます。これにより、情報をクライアントにピザを1つずつストリーム配信できます。 - Server.kt ファイルに移動し、現在の注文をリストのマップに格納することでこの機能を実装します。 kotlin
各クライアントインスタンスに対して
PizzaShopImpl
の新しいインスタンスが作成されることに注意してください。これにより、クライアントの状態を分離することでクライアント間の競合を回避します。ただし、これは単一サーバーインスタンス内のスレッドセーフティ、特に同じインスタンスが複数のサービスによって同時にアクセスされる場合の課題には対処しません。 - Client.kt ファイルで、2つの異なるクライアントIDを使用して複数の注文を送信します。 kotlin
次に、
Coroutines
ライブラリとcollect
メソッドを使用して結果を反復処理します。kotlin - サーバーとクライアントを実行します。クライアントを実行すると、結果が段階的に表示されるのがわかります。
動作する例を作成したので、次にすべてがどのように機能するかをさらに深く掘り下げてみましょう。特に、Kotlin RPCと主要な2つの代替手段であるRESTおよびgRPCを比較検討します。
RPC vs. REST
RPCのアイデアはRESTよりもかなり古く、少なくとも1981年に遡ります。RESTと比較して、RPCベースのアプローチは統一インターフェース(HTTPリクエストタイプなど)に制約されず、コードでの扱いがはるかに簡単で、バイナリメッセージングのおかげでパフォーマンスが向上する可能性があります。
しかし、RESTには3つの大きな利点があります。
- ブラウザのJavaScriptクライアントから直接使用でき、したがってシングルページアプリケーションの一部として使用できます。RPCフレームワークは生成されたスタブとバイナリメッセージングに依存するため、JavaScriptエコシステムにはうまく適合しません。
- RESTは、機能がネットワークに関わる場合、それを明確にします。これにより、Martin Fowlerが指摘した分散オブジェクトのアンチパターンを回避するのに役立ちます。これは、チームがローカルプロシージャ呼び出しをリモート化することによるパフォーマンスと信頼性の影響を考慮せずに、OO設計を2つ以上の部分に分割した場合に発生します。
- REST APIは、作成、ドキュメント化、監視、デバッグ、テストを比較的容易にする一連の規約に基づいて構築されています。これをサポートする膨大なツールエコシステムが存在します。
これらのトレードオフは、Kotlin RPCが2つのシナリオで最もよく使用されることを意味します。第一に、Compose Multiplatformを使用するKMPクライアントにおいて、第二に、クラウド上の連携するマイクロサービス間です。Kotlin/Wasmの将来の発展により、kotlinx.rpc
はブラウザベースのアプリケーションにより適用可能になる可能性があります。
Kotlin RPC vs. Google RPC
Google RPCは、現在のソフトウェア業界で支配的なRPCテクノロジーです。Protocol Buffers (protobuf) と呼ばれる標準は、言語に依存しないインターフェース定義言語 (IDL) を使用してデータ構造とメッセージペイロードを定義するために使用されます。これらのIDL定義は、様々なプログラミング言語に変換でき、コンパクトで効率的なバイナリ形式を使用してシリアライズされます。QuarkusやMicronautのようなマイクロサービスフレームワークは、すでにgRPCをサポートしています。
Kotlin RPCがgRPCと競合することは困難であり、Kotlinコミュニティにとって利益もありません。幸いにも、そのような計画はありません。むしろ、kotlinx.rpc
がgRPCと互換性があり、相互運用可能であることが意図されています。kotlinx.rpc
サービスがgRPCをネットワークプロトコルとして使用したり、kotlinx.rpc
クライアントがgRPCサービスを呼び出したりすることが可能になります。kotlinx.rpc
は、デフォルトオプションとして独自のkRPCプロトコルを使用しますが(現在の例がそうであるように)、代わりにgRPCを選択することを妨げるものは何もありません。
次のステップ
Kotlin RPCは、サービスを作成および利用するためのRESTやGraphQLに代わる選択肢を提供し、Kotlinエコシステムを新しい方向に拡張します。Ktor、コルーチン、kotlinx-serialization
などの実績のあるライブラリとフレームワークの上に構築されています。Kotlin MultiplatformやCompose Multiplatformの利用を検討しているチームにとって、分散メッセージングのためのシンプルで効率的なオプションとなるでしょう。
この紹介で興味を持たれた場合は、公式のkotlinx.rpc
ドキュメントとサンプルをぜひご確認ください。
kotlinx.rpc
ライブラリはまだ初期段階にあるため、ぜひ探索し、フィードバックを共有してください。バグや機能リクエストはYouTrackで、一般的な議論はSlack(アクセスリクエスト)で行われています。