C#でgRPCサービスを開発する――.NETとgRPCクライアントEvans:スキマ時間にこっそり学ぶ「gRPC」入門(2)
第2回からは、共通の機能を持ったgRPCサービスの開発を通じ、各種のプラットフォームとプログラミング言語によるサービス実装、クライアントからの呼び出し方法の基本について紹介していきます。第2回は、C#と.NETにおけるgRPCサーバの開発手順を紹介し、その動作検証に使える汎用のgRPCクライアントも紹介します。
本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。
.NETにおけるgRPCアプリケーションの概要
.NETは、Microsoftが提供するアプリケーションの開発、実行のためのプラットフォームです。.NET Frameworkという名称で、長らくWindows専用と言えるような状況でしたが、.NET Coreのリリースによりマルチプラットフォーム化とオープンソース化が進みました。現在は、WindowsをはじめとしてmacOS、Linuxなどで開発が可能で、これらに加えてiOSやAndroid OSのアプリケーションも開発が可能になっています。最新版は、2022年11月にリリースされた.NET 7です。
【補足】.NET Coreと.NET
本稿作成時点でバージョン7がリリースされている.NETは、バージョン3.1までは.NET Coreと呼ばれており、その後継のバージョン5から.NETというCoreの語を含まない名称になりました。.NET Frameworkも、Windows専用の実装として.NETに含まれており、サポートが継続されています。
.NETでは、多岐にわたるアプリケーションの開発が可能で、その一つとしてgRPCアプリケーションもサポートされています。.NETでサポートされているプラットフォーム(Windows、macOS、Linux)であれば、それらで稼働するgRPCアプリケーションの開発が可能です。なお、プログラミング言語はC#のみがサポートされます。
gRPCでは、通信路はHTTP/2が標準ですが、.NETにおけるgRPCでは既定であるHTTP/2(application/grpc)に加えてHTTP/1.1(application/grpc-web)もサポートされます。後者はHTTP/2をサポートしない環境で有用ですが、クライアントサイドストリーミングと双方向ストリーミングが使えないという制約があります。
.NETの組み込みサーバ(Kestrel、IISなど)ではHTTP/2をサポートしますので、本稿ではHTTP/2を使う既定の実装について紹介していきます。
【補足】gRPCにおける通信方式
gRPCでは、以下の4つの通信方式がサポートされます。ただし、実際に使えるかというのは、通信路のプロトコル、プラットフォーム、プログラミング言語における実装に依存します(基本となるUnaryは必ず使える)。
- Unary……1リクエストに対して1レスポンスが返る
- クライアントサイドストリーミング……複数リクエストに対して1レスポンスが返る
- サーバサイドストリーミング……1リクエストに対して複数レスポンスが返る
- 双方向ストリーミング……複数リクエストに対して複数レスポンスが返る
開発環境の準備
今回から、具体的なプログラミング言語を使ったgRPCサーバとクライアントを作成していきます。作成するアプリケーションは、連載第1回で作成したプロトコル定義ファイルを使った、書籍情報検索サービスのサーバとクライアントです。そのためにいくつかの必要なツールをあらかじめ準備しておきます。macOSを基本に解説しますが、Windowsにも必要に応じて触れます。なお、あくまでも学習用としての位置付けなので、サーバとクライアントは同じホストに配置します。
VSCodeをインストールする
ソースコードの作成とコンパイル、実行のために、本連載ではコードエディタとしてVSCode(Visual Studio Code)の利用を想定します。複数のプロジェクトの取り回しに便利ですし、VSCode内に複数のターミナルを開いて利用できるからです。VSCodeをインストールしていないという方は、この機会にVSCodeをインストールして、日本語化も実施しておくことをおすすめします。VSCodeのインストールについては、@ITの記事を参考にしてください(Windows版です)。
プログラマー以外にもおすすめ 「Visual Studio Code」のインストールから基本設定まで
また、今回はC#言語による開発になりますので、C#のための拡張機能「C# for Visual Studio Code」をインストールしておくこともおすすめします。
Evansをインストールする
gRPCクライアントとサーバを同時に開発したり、あるいはサーバのみを開発したりするという場合には、サーバの動作検証を専用クライアントなしで実施できた方が便利です。そこで、汎用(はんよう)のgRPCクライアントを準備しておきます。
汎用のgRPCクライアントには幾つかありますが、今回はktr0731氏によるEvansをインストールします。EvansはREPL(Read-Eval-Print Loop)モードを備えているので、対話式で操作することもできますし、コンパイル済みのバイナリが用意されているので、インストールするだけですぐに利用を開始できます。
EvansのバイナリはmacOS、Linux、Windowsに対して用意されています。このうちLinux用とWindows用のバイナリはEvansのGitHubリポジトリからダウンロードし、アーカイブを展開してできるバイナリを手動で配置します。例えばWindows(Intel/AMDプラットフォーム64bit)の場合、evans_windows_amd64.tar.gzをダウンロードして、展開してできるevans.exeを適当な場所に配置します。
macOSの場合は、パッケージマネジャーHomebrewでのインストールが推奨されているので、brewコマンドを利用します。Homebrewがインストールされていない場合には、下記サイトの「インストール」と表示された箇所にあるコマンドを実行してください。
macOS(またはLinux)用パッケージマネジャー - Homebrew
Homebrewが準備できたら、以下のようにEvansをインストールします。
% brew tap ktr0731/evans % brew install evans
【補足】Command Line Toolsが期限切れ(outdated)と表示されるとき
brewコマンドの実行で「Error: Your Command Line Tools are too outdated.」と表示される場合は、いったんCommand Line Toolsを削除し、再インストールしてください。
% sudo rm -rf /Library/Developer/CommandLineTools % sudo xcode-select --install
プラットフォームにかかわらず、evans -vコマンドを実行して、以下のようにバージョンが表示されればインストールは成功です。
% evans -v evans 0.10.9
.NET 6.0 SDKをインストールする
ここからは、今回以降のテーマ(C#と.NETによるgRPCサーバとクライアントの開発)に必要な準備になります。ここでは、.NETの開発キットである.NET 6.0 SDKをインストールします。プラットフォームにかかわらず、下記ページから.NET 6をダウンロードし、インストールしてください。本稿作成時点で.NET 7がリリースされていますが、ここではLTS(Long Term Support)版である.NET 6をインストールすることにします(.NET 7はStandard Term Supportです)。
Download .NET (Linux, macOS, and Windows)
上記のダウンロードページにおいて、対象のプラットフォームのページとなっていることを確認します(ページ上部に「For macOS」「For Windows」などと表示される)。そこで表示される[.NET SDK x64]をクリックします(Arm64版と、Windowsではx86版もプルダウンから選択可能)。
これで、インストーラ(macOSではパッケージファイル、Windowsでは実行形式ファイル)がダウンロードされます。ダウンロードされたファイルをダブルクリックするとインストーラが起動し、.NETのインストールが始まります。インストール自体に難しいところはありませんから、指示通りに進めます。
最終的には、.NET SDK、.NET Runtime、ASP.NET Core Runtime(Windowsの場合は.NET Windows Desktop Runtimeも)がインストールされます。ターミナル(Windowsの場合はPowerShell、以降同様)を開いて「dotnet --version」コマンドを実行し、「6.0.403」のようにバージョンが表示されれば正常にインストールされています(バージョンの細かな数字は変化することがあります)。
gRPCサーバアプリケーションの作成
gRPCサーバとクライアントは、基本的に別のアプリケーションとして作成していきます。今回作成するのは、サーバアプリケーションのベースです。.NETにはgRPCサーバのテンプレートが用意されているので、テンプレートを使ってベースとなるアプリケーションを作成し、慣れるまではそれに手を入れていくのがよいでしょう。書籍情報検索サービスの実装と、対応するクライアントアプリケーションの作成は次回で紹介します。
サーバアプリケーションを作成する
プロジェクトを配置するフォルダを用意して(ここではatmarkit_grpc_dotnetとしました)、ターミナル上でカレントフォルダをここに移動しておきます。そして、以下のようにdotnet newコマンドでプロジェクトを作成します。
% dotnet new grpc -o GrpcBookInfo テンプレート "ASP.NET Core gRPC Service" が正常に作成されました。 …略…
コマンド中の「grpc」はgRPCサーバアプリケーションのテンプレートを指定しています。「-o GrpcBookInfo」オプションは、自動生成するターゲットの指定です。フォルダ名、ファイル名、名前空間などが、ここで指定された名前に基づいて作成されます。
いろいろとメッセージが表示されますが、重要なのは「テンプレート "ASP.NET Core gRPC Service" が正常に作成されました。」のみです。これで、サンプルとしてGreeterサービスが用意されたgRPCサーバアプリケーションが作成されます。Greeterは、リクエストの内容をそのままレスポンスとして返すSeyHelloメソッドを1つだけ持ったサービスです。
VSCodeで作業するために、生成されたプロジェクトのフォルダ(GrpcBookInfo)をワークスペースに追加しておきます。以降は、VSCodeのメニューから[ターミナル]−[新しいターミナル]を選択し、ターミナルを開いて作業します。
【補足】アセットの追加は不要
フォルダをワークスペースに追加すると、ウィンドウ右下に「Required assets to build...」というポップアップが表示されます(「作成者を信頼するか?」と聞かれたら「はい」と答えてください)。これは、VSCodeによるビルドとデバッグに必要なアセットをプロジェクトに追加するかという問い合わせです。本稿では、dotnet buildコマンドやdotnet runコマンドによる実行になるので、基本的にプロジェクトへの追加は不要です。
開発用証明書を受け入れる
今回、サーバとクライアントは同一ホストで動作するので、サーバのホスト名は自らを表すlocalhostとなります。ここでは、localhostに対する開発用証明書を受け入れるために、以下のようにdotnet dev-certsコマンドで設定します。
% dotnet dev-certs https --trust Trusting the HTTPS development certificate was requested. If the certificate is not already trusted we will run the following command: …略… Password: キーチェーンのパスワードを入力 The HTTPS developer certificate was generated successfully.
macOSの場合は、パスワードを要求されますので、キーチェーンのパスワードを入力します。以前に同様に作業したことがあれば、「A valid HTTPS certificate is already present.」と表示されます。Windowsの場合は、「Security Warning」のダイアログボックスが表示されますので、[Yes]をクリックします。これで、開発用証明書の使用に同意したことになり、localhost上でのHTTPSによるサーバアクセスが可能になります。
【補足】macOSではHTTP/2とTLSの組み合わせはサポートされない
実は、macOSでは、本稿作成時点においてKestrelにおけるHTTP/2とTLS(Transport Layer Security)の組み合わせをサポートしていません。そのため、既定ではHTTPSによる接続はできず、プロジェクトのビルド時にエラーとなります。
よって、上の説明もmacOSにおいて必須の操作ではありませんが、予定されているサポートに備えて手順だけは知っておくと良いでしょう。本稿のサンプルでは、暫定の措置として、TLSを使用しない接続を選択しています。
"applicationUrl": "https://localhost:5279;http://localhost:5289", 既定 ↓ "applicationUrl": "http://localhost:5289", 変更後(https://〜を削除)
Windowsでは、このような制約はありません。また、将来のサポートによりTLS上でのHTTP/2が利用可能になるか、本番環境での運用では、既定の通り、「https://〜」のURLを優先して記述すべきです。
ログレベルを設定する
のちほど、実際にアプリケーションを起動して動作を確認しますが、その際にログメッセージをinfoレベルから出力したいので、GrpcBookInfo/appsettings.Development.jsonファイルを以下のように修正しておきます。
"Microsoft.AspNetCore": "Warning", 行末にカンマを追加 "Microsoft.AspNetCore.Hosting": "Information", 行を追加 "Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information" 行を追加
サーバを起動しEvansからアクセスする
ここで、作成したサーバアプリケーションが正しく動作するか、Evansから既定のGreeterサービスを呼び出して検証しておきましょう。gRPCサーバは、GrpcBookInfoフォルダで、dotnet runコマンドでビルド、起動します。以下のように、informationレベルのログが出力されるのを確認してください。
なお、ビルドがはじめての場合は、GrpcBookInfo/obj/Debug/net6.0/Protosフォルダ以下にC#のソースファイルが2個生成されます(Greet.cs、GreetGrpc.cs)。これらはそれぞれ、Greeterサービスのメッセージに相当するPOCOファイル、そしてシリアライズなどの処理を実装したクラスファイルです。サービスの処理を実装する際には、ここで定義されているクラスなどを参照することになります。
% dotnet run ビルドしています... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5299 このポート番号を使用 …略…
ここでターミナルをもう一つ開いて、evansコマンドにホスト名とポート番号、そしてプロトコル定義ファイルgreet.protoを指定してREPLモードで起動します。ポート番号には、上記の起動ログに表示されているものを指定してください。
% evans --proto Protos/greet.proto --host localhost --port 5299 repl …略… more expressive universal gRPC client greet.Greeter@localhost:5299>
プロンプトが表示されたら、サーバへの接続は成功しています。続けて、既定で定義されているパッケージgreetとサービスGreeterを選択し、手続きSayHelloの呼び出しを実行します。
greet.Greeter@localhost:5299> package greet パッケージgreetの選択 greet@localhost:5299> service Greeter サービスGreeterの選択 greet.Greeter@localhost:5299> call SayHello 手続きSayHelloの呼び出し name (TYPE_STRING) => Yamauchi SayHelloの引数(文字列)を入力 { 手続きの戻り値(JSON) "message": "Hello Yamauchi" }
package、service、callともにEvansのコマンドです。Evansでは、コマンド名まで入力した時点でパラメーターの選択肢が表示されますので、Tabキーで選択してEnterキーを押すだけでコマンドが完成して便利です。手続きSayHelloは、引数の文字列に"Hello"を前置して返すだけのシンプルなものです。なお、結果は分かりやすいようにJSON形式で表示されます。
このとき、サーバアプリケーション側のログメッセージには、以下のようにリクエストとレスポンスの様子が出力されています。URLが、「パッケージ.サービス/手続き」となっていることに注意してください。POSTが指定されているので、リクエストパラメーターはリクエストボディーとして送信されます。
info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/2 POST http://localhost:5299/greet.Greeter/SayHello application/grpc - …略… info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/2 POST http://localhost:5299/greet.Greeter/SayHello application/grpc - - 200 - application/grpc 66.4818ms
サーバアプリケーションはCtrl+Cを入力して停止します。Evans側は、exitコマンドを入力して終了します。
サーバアプリケーションの構成
動作が確認できたところで、次回に紹介する書籍情報検索サービスの追加を踏まえながら、サーバアプリケーションの構成を2つのファイルに絞って見てみます。他のファイルは、次回で紹介します。
プロトコル定義ファイルgreet.proto
プロトコル定義ファイルgreet.protoは、Protosフォルダに置かれます。以下のようなシンプルな内容で、基本的に連載第1回の解説で読み解けるものです。上記で実行した通り、パッケージgreet、サービスGreeter、HelloRequestメッセージを受け取ってHelloReplyメッセージを返す手続きSayHelloが定義されています。
syntax = "proto3"; option csharp_namespace = "GrpcBookInfo"; (1) package greet; // パッケージ service Greeter { // サービス rpc SayHello (HelloRequest) returns (HelloReply); // 手続き } message HelloRequest { // メッセージ string name = 1; } message HelloReply { // メッセージ string message = 1; }
(1)の書式は初出ですが、option文はプログラミング言語などに特有なオプションを定義します。この場合は、C#の名前空間の指定になっています。この名前に基づき、ビルド時に自動生成されるソースファイルのnamespace文が生成されます。後述するサービスを実装するファイルのuse文と名前空間が一致する必要があるので、変更すべきではありません。
サービス記述ファイルGreeterService.cs
Greeterサービスの実装は、ServicesフォルダにあるGreeterService.csファイルに記述されます。これに倣って、サービスを実装するファイルの名称は「<サービス名>Service.cs」とするのがよいでしょう。以下のような内容になっています。
using Grpc.Core; using GrpcBookInfo; (1) namespace GrpcBookInfo.Services; (2) public class GreeterService : Greeter.GreeterBase (3) { private readonly ILogger<GreeterService> _logger; (4) public GreeterService(ILogger<GreeterService> logger) { _logger = logger; } public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) (5) { return Task.FromResult(new HelloReply (6) { Message = "Hello " + request.Name }); } }
(1)は、greet.protoファイルのoption文で指定されていた名前空間です。ビルド時に自動生成されたファイルには、この名前空間が指定されているので、基本的に同一でなければなりません。(2)の方は、サービスを表すファイルスコープ名前空間を「.Service」を付加して指定することになっています。
(3)は、サービスのクラスの定義です。これに倣って、クラス名は「<サービス名>Service」とするとよいでしょう。基底クラス名は「<サービス名>.<サービス名>Base」とすることになっています。
(4)は、ロガーのフィールドと、依存性注入のためのコンストラクタになります。
(5)は、手続きSayHelloに対応するメソッドの定義です。gRPCの手続きメソッドの決まりは、大まかに以下のようになっています。詳細は、次回でBookInfoサービスを実装する際に触れます。
- 手続きの引数であるHelloRequest型のオブジェクトと、ServerCallContextオブジェクトを引数とする
- 手続きの戻り値であるHelloReply型のジェネリクスによるTaskオブジェクトを、メソッドの戻り値とする
(6)のように、戻り値はオブジェクト初期化子を用いて生成したHelloReplyオブジェクトを、Task.FromResultメソッドに渡して生成することになっています。
当面は、これらを定型的な記述として理解しておきましょう。
まとめ
今回は、C#によるgRPCサーバアプリケーションを既定のテンプレートから作成し、それを実行してクライアントからアクセスすることで動作とファイル構成を確認しました。
次回は、これを踏まえてサーバアプリケーションに書籍情報検索サービスを実装し、それに対応するgRPCクライアントアプリケーションを同じくC#で作成していきます。
筆者紹介
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・Twitter: @yyamada(https://twitter.com/yyamada)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.