Skip to content

Kotlin RPC の最初のステップ

コード例: tutorial-kotlin-rpc-app

使用されているプラグイン:

Routing
ルーティングは、サーバーアプリケーションで受信リクエストを処理するためのコアプラグインです。
, kotlinx.serialization, kotlinx.rpc

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フレームワークは以下を提供する必要があります。

  1. リモートで呼び出されるプロシージャを宣言する方法。オブジェクト指向プログラミングでは、インターフェースが論理的な選択肢です。これは、現在の言語が提供するインターフェース構造であるか、W3Cで使われているWeb IDLのような言語に依存しない標準である可能性があります。
  2. パラメータと戻り値に使用される型を指定する手段。ここでも、言語に依存しない標準を使用できます。しかし、現在の言語で標準のデータ型宣言にアノテーションを付ける方が簡単な場合もあります。
  3. ヘルパークラス( )は、プロシージャ呼び出しをネットワーク経由で送信できる形式に変換し、結果の戻り値をアンパックするために使用されます。これらのスタブは、コンパイルプロセス中または実行時に動的に作成できます。
  4. 基盤となる がヘルパークラスを管理し、リモートプロシージャ呼び出しのライフサイクルを監督します。サーバー側では、このランタイムは継続的にリクエストを処理できるように、何らかのサーバーに組み込む必要があります。
  5. 呼び出されるプロシージャを表現し、送信されるデータをシリアライズし、ネットワーク上で情報を変換するためのプロトコルを選択(または定義)する必要があります。過去には、ゼロから新しいプロトコルを定義した技術(CORBAのIIOP)もあれば、再利用に焦点を当てた技術(SOAPのHTTP POST)もありました。

マーシャリング vs. シリアライゼーション

RPCフレームワークでは、「 」と「 」という言葉を使います。これは、ネットワーク経由で送信される情報をパックおよびアンパックするプロセスです。これはシリアライゼーションのスーパーセットと考えることができます。マーシャリングでは、オブジェクトをシリアライズしますが、呼び出されるプロシージャと、その呼び出しが行われたコンテキストに関する情報もパッケージ化する必要があります。

RPCのコアコンセプトを導入したところで、サンプルアプリケーションを構築してkotlinx.rpcでどのように適用されるかを見てみましょう。

Hello, kotlinx.rpc

ネットワーク経由でピザを注文するアプリケーションを作成してみましょう。コードをできるだけシンプルに保つため、コンソールベースのクライアントを使用します。

プロジェクトを作成する

まず、クライアントとサーバーの両方の実装を含むプロジェクトを作成します。

より複雑なアプリケーションでは、クライアントとサーバーで個別のモジュールを使用するのがベストプラクティスです。ただし、このチュートリアルでは簡略化のため、両方で単一のモジュールを使用します。

  1. IntelliJ IDEA を起動します。
  2. ようこそ画面で、New Project をクリックします。

    または、メインメニューから File | New | Project を選択します。

  3. Name フィールドに、プロジェクト名として KotlinRpcPizzaApp を入力します。 IntelliJ 新規Kotlinプロジェクトウィンドウ
  4. 残りのデフォルト設定はそのままにして、Create をクリックします。

通常、すぐにプロジェクトビルドファイルを構成するでしょう。しかし、それは技術の理解を深めるものではない実装の詳細であるため、そのステップには最後に立ち戻ります。

共有型を追加する

あらゆるRPCプロジェクトの核となるのは、リモートで呼び出されるプロシージャを定義するインターフェースと、それらのプロシージャの定義に使用される型です。

マルチモジュールプロジェクトでは、これらの型を共有する必要があります。しかし、この例では、このステップは不要です。

  1. src/main/kotlin フォルダーに移動し、model という新しいサブパッケージを作成します。
  2. model パッケージ内に、次の実装を含む新しい PizzaShop.kt ファイルを作成します。
    kotlin

    インターフェースには、kotlinx.rpc ライブラリの @Rpc アノテーションが必要です。

    ネットワーク経由で情報を転送するために kotlinx.serialization を使用しているため、パラメータで使用される型には Serializable アノテーションを付ける必要があります。

クライアントを実装する

  1. src/main/kotlin に移動し、新しい Client.kt ファイルを作成します。
  2. Client.kt を開き、次の実装を追加します。
    kotlin

RPC呼び出しの準備と実行には、わずか25行のコードしか必要ありません。明らかに多くのことが行われているため、コードをセクションに分けて見ていきましょう。

kotlinx.rpc ライブラリは、クライアント側でランタイムをホストするために

Ktor クライアント
リクエストを送信し、レスポンスを受信する最初のクライアントアプリケーションを作成します。
を使用します。ランタイムはKtorに結合されておらず、他の選択肢も可能ですが、これにより再利用が促進され、既存のKMPアプリケーションにkotlinx.rpcを簡単に統合できるようになります。

KtorクライアントとKotlin RPCはどちらもコルーチンを中心に構築されているため、runBlockingを使用して最初のコルーチンを作成し、その中でクライアントの残りの部分を実行します。

kotlin

TIP

runBlocking は、本番コードではなく、スパイクやテストのために設計されていることに注意してください。

次に、Ktorクライアントのインスタンスを標準的な方法で作成します。kotlinx.rpcは、情報の転送に内部的に

WebSockets
WebSocketsプラグインを使用すると、サーバーとクライアント間で多方向通信セッションを作成できます。
プラグインを使用します。installKrpc()関数を使用して、それが読み込まれていることを確認するだけで十分です。

kotlin

このKtorクライアントを作成したら、次にリモートプロシージャを呼び出すためのKtorRpcClientオブジェクトを作成します。サーバーの場所と情報の転送に使用されるメカニズムを設定する必要があります。

kotlin

この時点で、標準的なセットアップが完了し、問題領域に特化した機能を使用する準備が整いました。クライアントを使用して、PizzaShopインターフェースのメソッドを実装するクライアントプロキシオブジェクトを作成できます。

kotlin

その後、リモートプロシージャ呼び出しを行い、その結果を使用できます。

kotlin

この時点で、非常に多くの作業が自動的に行われていることに注意してください。呼び出しの詳細とすべてのパラメータはメッセージに変換され、ネットワーク経由で送信され、その後、戻り値が受信されてデコードされます。この透過的な処理が、初期設定の成果です。

最後に、通常通りクライアントをシャットダウンする必要があります。

kotlin

サーバーを実装する

サーバー側の実装は2つの部分に分かれます。まず、インターフェースの実装を作成する必要があり、次に、それをサーバー内でホストする必要があります。

  1. src/main/kotlin に移動し、新しい Server.kt ファイルを作成します。
  2. Server.kt を開き、次のインターフェースを追加します。
    kotlin

    これは現実世界の実装ではありませんが、デモを動かすには十分です。

    実装の2番目の部分はKtorに基づいています。

  3. 同じファイルに次のコードを追加します。

    kotlin

    内訳は次のとおりです。

    まず、構成に使用される指定された拡張関数を使用して、Ktor/Nettyのインスタンスを作成します。

    kotlin

    次に、Ktor Application型を拡張するセットアップ関数を宣言します。これにより、kotlinx.rpc プラグインがインストールされ、1つ以上のルートが宣言されます。

    kotlin

    ルーティングセクション内では、Ktor Routing DSLに対するkotlinx.rpc拡張機能を使用してエンドポイントを宣言します。クライアント側と同様に、URLを指定してシリアライゼーションを設定します。ただし、このケースでは、私たちの実装はそのURLで受信リクエストをリッスンします。

    kotlin

    registerService を使用して、インターフェースの実装をRPCランタイムに提供することに注意してください。単一のインスタンスよりも多くが必要になる場合もありますが、それは後続の記事のトピックです。

依存関係を追加する

アプリケーションを実行するために必要なコードはすべて揃いましたが、現時点ではコンパイルすらできず、実行には程遠い状態です。kotlinx.rpcプラグインと共にKtor Project Generatorを使用するか、ビルドファイルを manually で構成することもできます。これもそれほど複雑ではありません。

  1. build.gradle.kts ファイルに、次のプラグインを追加します。
    kotlin

    Kotlinプラグインの理由は明らかです。その他について説明します。

    • kotlinx.serialization プラグインは、KotlinオブジェクトをJSONに変換するためのヘルパー型を生成するために必要です。kotlinx.serialization がリフレクションを使用しないことを覚えておいてください。
    • Ktorプラグインは、アプリケーションとそのすべての依存関係をバンドルするfat JARをビルドするために使用されます。
    • RPCプラグインは、クライアント側のスタブを生成するために必要です。
  2. 次の依存関係を追加します。
    kotlin

    これにより、Ktorクライアントとサーバー、kotlinx.rpcランタイムのクライアント側とサーバー側の部分、およびkotlinx.rpckotlinx-serializationを統合するためのライブラリが追加されます。

    これにより、プロジェクトを実行してRPC呼び出しを開始できるようになります。

アプリケーションを実行する

デモを実行するには、以下の手順に従ってください。

  1. Server.kt ファイルに移動します。
  2. IntelliJ IDEA で、main() 関数の横にある実行ボタン (IntelliJ IDEA実行アイコン) をクリックしてアプリケーションを起動します。

    Run ツールパネルに次のような出力が表示されるはずです。

    IntelliJ IDEAでのサーバー実行出力
  3. Client.kt ファイルに移動してアプリケーションを実行します。コンソールに次の出力が表示されるはずです。
    shell

例を拡張する

最後に、将来の開発のための強固な基盤を確立するために、サンプルアプリケーションの複雑さを向上させましょう。

  1. PizzaShop.kt ファイルで、クライアントIDを含むようにorderPizzaメソッドを拡張し、指定されたクライアントの保留中のすべての注文を返すviewOrdersメソッドを追加します。
    kotlin

    ListSet ではなく Flow を返すことで、コルーチンライブラリの利点を活用できます。これにより、情報をクライアントにピザを1つずつストリーム配信できます。

  2. Server.kt ファイルに移動し、現在の注文をリストのマップに格納することでこの機能を実装します。
    kotlin

    各クライアントインスタンスに対してPizzaShopImplの新しいインスタンスが作成されることに注意してください。これにより、クライアントの状態を分離することでクライアント間の競合を回避します。ただし、これは単一サーバーインスタンス内のスレッドセーフティ、特に同じインスタンスが複数のサービスによって同時にアクセスされる場合の課題には対処しません。

  3. Client.kt ファイルで、2つの異なるクライアントIDを使用して複数の注文を送信します。
    kotlin

    次に、Coroutines ライブラリと collect メソッドを使用して結果を反復処理します。

    kotlin
  4. サーバーとクライアントを実行します。クライアントを実行すると、結果が段階的に表示されるのがわかります。 クライアント出力が結果を段階的に表示

動作する例を作成したので、次にすべてがどのように機能するかをさらに深く掘り下げてみましょう。特に、Kotlin RPCと主要な2つの代替手段であるRESTおよびgRPCを比較検討します。

RPC vs. REST

RPCのアイデアはRESTよりもかなり古く、少なくとも1981年に遡ります。RESTと比較して、RPCベースのアプローチは統一インターフェース(HTTPリクエストタイプなど)に制約されず、コードでの扱いがはるかに簡単で、バイナリメッセージングのおかげでパフォーマンスが向上する可能性があります。

しかし、RESTには3つの大きな利点があります。

  1. ブラウザのJavaScriptクライアントから直接使用でき、したがってシングルページアプリケーションの一部として使用できます。RPCフレームワークは生成されたスタブとバイナリメッセージングに依存するため、JavaScriptエコシステムにはうまく適合しません。
  2. RESTは、機能がネットワークに関わる場合、それを明確にします。これにより、Martin Fowlerが指摘した分散オブジェクトのアンチパターンを回避するのに役立ちます。これは、チームがローカルプロシージャ呼び出しをリモート化することによるパフォーマンスと信頼性の影響を考慮せずに、OO設計を2つ以上の部分に分割した場合に発生します。
  3. 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アクセスリクエスト)で行われています。