gRPC と .NET 入門

gRPC と .NET 入門

本質的には、API はサーバーとクライアント間のプロトコルであり、クライアントのリクエストに基づいてサーバーが特定のデータを提供する方法を指定します。

最終更新 2022/02/18 21:55
Mohamad Lawand
読了目安 9 分
カテゴリ
.NET
タグ
.NET C# gRPC
  • 著者 | Mohamad Lawand
  • 翻訳者 | 張衛濱
  • 企画 | 丁曉昀

本質的に、APIはサーバーとクライアント間のプロトコルであり、クライアントのリクエストに基づいてサーバーが特定のデータを提供する方法を指定します。

APIを構築する際、さまざまなテクノロジーが考えられます。要件によって、API開発に選択する技術も変化します。現在の時代において、APIを作成するための主要な技術は次の2つです。

  • gRPC
  • REST

どちらの技術も、HTTPを転送メカニズムとして使用します。同じ基本的な転送メカニズムを使用しているにもかかわらず、その実装はまったく異なります。

まず、これら2つの技術を比較し、その後gRPCについて詳しく見ていきます。

REST

RESTは、プロトコルや標準ではなく、アーキテクチャ上の制約のセットです。API開発者はさまざまな方法でRESTを実装できます。

APIがRESTfulと見なされるためには、いくつかの制約に従う必要があります。

  • クライアント-サーバーアーキテクチャ:すべてのリクエストは、転送メカニズムとしてHTTPを使用する必要があります。

  • ステートレス:APIはステートレスである必要があります。つまり、サーバーはサーバー側にクライアントセッションの状態を保存してはなりません。クライアントからサーバーへの各リクエストには、そのリクエストを理解するために必要なすべての情報を含める必要があります。サーバーは、サーバー側に保存されたコンテキストを使用してはなりません。

  • キャッシュ可能:クライアントとサーバー間を流れるすべてのデータはキャッシュ可能である必要があります。つまり、後で取得して使用できるように保存できることを意味します。

  • 統一インターフェース:クライアントとサーバーの間には、情報が標準形式で転送されるためのインターフェースが必要です。

  • 階層化システム:クライアントのリクエストとサーバーのレスポンスの間に関与するすべてのサーバーは、その責務に従って編成される必要があり、その編成方法はリクエストやレスポンスに影響を与えてはなりません。

gRPC

gRPCは、RPC(Remote Procedure Call)プロトコルの強固な基盤の上に構築されており、APIの領域にも参入しています。gRPCはGoogleによって開発された無料のオープンソースフレームワークであり、API通信にHTTP/2を使用し、API設計者からHTTP実装を隠蔽します。

gRPCには多くの特徴があり、マイクロサービスやWeb/モバイルAPI通信の両方において、次世代Webアプリケーションの基本的な構成要素となっています。

  • 相互運用性(Interoperability):現在のHTTPバージョンに関係なく、インフラストラクチャが変更されても(例:HTTP 2からHTTP 3へのアップグレード)、プロトコルは適応して変更できなければなりません。

  • 階層化アーキテクチャ:テクノロジースタックの中核部分は、それを使用するアプリケーションを壊すことなく、独立して進化およびアップグレードできなければなりません。

  • ペイロードの中立性(Agnostic):異なるサービスでは、Protobuf(Protocol buffer)、JSON、XMLなど、異なるメッセージタイプとエンコーディングが必要になる場合があります。gRPCはこれらすべてのフォーマットをサポートし、プラグイン可能な圧縮メカニズムを利用してペイロードを圧縮できます。

  • ストリーミング:gRPCでは、大規模なデータセットをサーバーからクライアントへ、またはその逆にストリーミングできます。

  • プラグイン可能(Pluggable):gRPCは、ヘルスチェック、障害回復、ロードバランシングなど、ニーズに応じてさまざまな機能やサービスをオンデマンドで挿入できます。フレームワークの実装は拡張ポイントを提供し、これらの機能を挿入できるようにします。

DockerやKubernetesと同様に、gRPCはCloud Native Computing Foundation(CNCF)の一部です。

要するに、gRPCの利点は次のとおりです。

  • モダンで高速

  • オープンソース

  • HTTP/2を活用

  • 言語中立

  • 認証やログの追加が容易

gRPCを使用するには:

  • Protobuf(Protocol Buffer)を使用してメッセージとサービスを定義する必要があります。

  • gRPCコードは自動生成され、具体的な実装を提供する必要があります。

  • .protoファイルは、サーバー側とクライアント側の両方で12の異なる言語をサポートします。

デフォルトでは、gRPCはGoogleのオープンソースであるProtocol Buffersメカニズムを使用して構造化データをシリアル化します。

  • 言語中立です。

  • 任意の現代的なプログラミング言語のコードを生成できます。

  • データ転送はバイナリで効率的です。

  • 高度に拡張可能です。

  • 大量のデータを送信できます。

  • APIの拡張と進化が可能です。

ケーススタディ:

現在の技術トレンドでは、マイクロサービスを構築することがより現代的な方法です。この例では、航空券販売システムを構築するプロセスを学びます。

上の図は、マイクロサービスベースの航空券販売システムを示しています。このタイプのアーキテクチャに関連するいくつかの重要なポイントに注意する必要があります。

  • マイクロサービスは通常、異なる言語で構築されます。つまり、予約管理サービスは.NET、支払い処理はJava、乗客情報はNode.jsで構築できます。

  • 各サービスは異なるビジネス機能を持っています。

異なる言語で書かれたマイクロサービスがあり、それらが相互に通信すると仮定します。これらのマイクロサービスが情報を交換したい場合、いくつかのことについて合意する必要があります。たとえば、

  • データ交換のためのAPI

  • データ形式

  • エラー形式

  • アクセス速度制限

RESTはAPIを構築するための最も一般的なソリューションです。ただし、この決定は、実装に関連する多くのアーキテクチャ上の考慮事項に依存します。

  • データモデルのタイプの設計

  • エンドポイントがどのようになるか

  • エラーの処理方法

  • クライアントが実行できる呼び出しの数

  • 認証の実装方法

これらの要因を考慮して、gRPCとRESTの違いを見てみましょう。

gRPC

  • 契約ファーストのAPI開発方法:契約(サービスとメッセージ)は*.protoファイルで定義され、これらはgRPCのコアです。これは、APIを言語中立的な方法で定義します。これらのファイルはその後、他のプログラミング言語でコード(強く型付けされたクライアントやメッセージクラスなど)を生成するために使用できます。

  • コンテンツはバイナリ:HTTP/2とProtobufはバイナリプロトコルであり、コンピュータと高性能向けに設計されています。

  • gRPCの設計はリモート操作の複雑さを隠蔽します。gRPCライブラリと関連するコード生成を使用することで、ルーティング、ヘッダー情報、シリアル化などを気にする必要がありません。クライアントでメソッドを呼び出す必要がある場合は、対応するメソッドを呼び出すだけです。

  • gRPCは双方向の非同期ストリームをサポート:gRPC呼び出しがストリームを確立すると、クライアントとサーバーはいつでも相互に非同期ストリームを送信できます。サーバーストリームとクライアントストリーム(この場合、レスポンスまたはリクエストのいずれかのみがストリーム)もサポートされています。

  • gRPCは、分散アプリケーションの高性能と高生産性向けに設計されています。

REST API

  • コンテンツファーストのAPI開発方法(URL、HTTPメソッド、JSON):可読性とフォーマットに重点を置いています。

  • コンテンツはテキストベース(HTTP 1.1とJSON)であり、人間が読めます。その結果、デバッグには非常に適していますが、パフォーマンスには優しくありません。

  • HTTPにより重点を置いています。低レベルの問題を考慮する必要がありますが、これはHTTPリクエストを大幅に制御できるため、良いことです。

  • CRUD指向。

  • 最も広いオーディエンス:すべてのコンピュータがHTTP/1.1とJSONを使用でき、習得が容易です。

これらの比較に基づいて、両方の方法にそれぞれ利点があることがわかります。しかし、gRPCがマイクロサービスのシナリオに強力な機能セットを提供していることがわかります。

gRPCを使用したサーバー-クライアントアプリケーションの作成

コーディングを始める前に、コンピュータに次のソフトウェアをインストールします。

ソフトウェアのインストールが完了したら、プロジェクト構造を作成する必要があります(この記事では、ターミナル/コマンドラインで直接dotnetコマンドを使用します)。

dotnet new grpc -n GrpcService

また、SSL信頼を構成する必要があります。

dotnet dev-certs https --trust

次に、VS Codeでこの新しいプロジェクトを開き、作成された内容を確認します。自動的に次の内容があることがわかります。

  • Protosフォルダー

  • Servicesフォルダー

Protosフォルダーには、greet.protoファイルがあります。前述のように、.protoファイルはAPIを言語中立的な方法で定義します。

このファイルから、GreeterサービスとSayHelloメソッドが含まれていることがわかります。Greeterサービスをコントローラー、SayHelloメソッドをアクションと考えることができます。.protoファイルの内容は次のとおりです。

// 使用可能な最新のスキーマを宣言
syntax = "proto3";

// このprotoの名前空間を定義。通常はGrpcサーバーと同じ
option csharp_namespace = "GrpcService";

package greet;

// サービスはクラスと考えることができる
service Greeter {
  // 挨拶を送信
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// リクエストメッセージは、プロパティを定義するC#のモデルに似ている
// ここの数字はプロパティの順序付けに使用される
message HelloRequest {
  string name = 1;
}

// レスポンスメッセージには挨拶が含まれる
message HelloReply {
  string message = 1;
}

SayHelloメソッドはHelloRequest(メッセージ)を受け取り、HelloReply(これもメッセージ)を返します。

GreeterServiceファイルでは、GreeterServiceクラスがあり、これはGreeter.GreeterBaseを継承しており、.protoファイルから自動生成されます。

SayHelloメソッドでは、リクエスト(HelloRequest)を受け取り、レスポンス(HelloReply)を返します。これらも.protoファイルによって自動的に生成されます。

コード自動生成は、.protoファイルの定義に基づいて必要なファイルを生成します。gRPCはコード生成、ルーティング、シリアル化の面で重労働をすべて代行します。私たちがする必要があるのは、基底クラスを実装し、メソッドの実装をオーバーライドすることだけです。

次に、gRPCサービスを実行してみましょう。

dotnet run

自動生成されたエンドポイントの結果から、WebブラウザをRESTのクライアントとして使用するようにgRPCを使用できないことがわかります。この場合、サービスと通信するためのgRPCクライアントを作成する必要があります。クライアントにとっても、gRPCは契約ファーストのRPCフレームワークであるため、.protoファイルが必要です。現在、Webブラウザはクライアント(.protoファイルを持っていない)について何も知らないため、リクエストの処理方法がわかりません。

customers.protoというカスタム.protoファイルを作成します。このファイルはProtosフォルダーに作成する必要があり、内容は次のとおりです。

syntax = "proto3";

option csharp_namespace = "GrpcService";

package customers;

service Customer {
    rpc GetCustomerInfo (CustomerFindModel) returns (CustomerDataModel);
}

message CustomerFindModel {
    int32 userId = 1; // bool, int32, float, double, string
}

message CustomerDataModel {
    string firstName = 1;
    string lastName = 2;
}

上記のファイルを保存した後、それを.csprojファイルに追加する必要があります。

<ItemGroup>
  <Protobuf Include="Protos\\customers.proto" GrpcServices="Server" />
</ItemGroup>

次に、アプリケーションをビルドします。

dotnet build

次のステップは、CustomerServiceクラスをServicesフォルダーに追加し、その内容を次のように更新することです。

public class CustomerService : Customer.CustomerBase
{
    private readonly ILogger<CustomerService> _logger;
    public CustomerService(ILogger<CustomerService> logger)
    {
        _logger = logger;
    }

    public override Task<CustomerDataModel> GetCustomerInfo(CustomerFindModel request, ServerCallContext context)
    {
       CustomerDataModel result = new CustomerDataModel();

       // これはデモ用のコードです
       // 実際のシナリオでは、この情報はデータベースから取得する必要があります
       // アプリケーション内のデータはハードコードされるべきではありません
       if(request.UserId == 1) {
           result.FirstName = "Mohamad";
           result.LastName = "Lawand";
       } else if(request.UserId == 2) {
           result.FirstName = "Richard";
           result.LastName = "Feynman";
       } else if(request.UserId == 3) {
           result.FirstName = "Bruce";
           result.LastName = "Wayne";
       } else {
           result.FirstName = "James";
           result.LastName = "Bond";
       }

        return Task.FromResult(result);
    }
}

次に、Startup.csクラスを更新して、新しく作成したサービスに新しいエンドポイントがあることをアプリケーションに通知する必要があります。そのためには、Configureメソッド(app.UserEndpoints内)で次のコードを追加します。

endpoints.MapGrpcService<CustomerService>();

MacOSでの注意事項:

MacOSはTLS上のHTTP/2をサポートしていないため、Program.csファイルを次のように更新する必要があります。

webBuilder.ConfigureKestrel(options =>
{
    // TLSなしのHTTP/2エンドポイントを設定
    options.ListenLocalhost(5000, o => o.Protocols =
        HttpProtocols.Http2);
});

次のステップは、クライアントアプリケーションを作成することです。

dotnet new console -o GrpcGreeterClient

次に、クライアントコンソールアプリケーションに必要なパッケージを追加して、gRPCを認識できるようにする必要があります。これはGrpcGreeterClientクラスで実現できます。

dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

クライアントがサーバーと同じ契約を持つ必要があるため、前の手順で作成した.protoファイルをクライアントアプリケーションに追加する必要があります。そのためには:

  1. まず、クライアントプロジェクトにProtosというフォルダーを追加する必要があります。

  2. gRPC greeterサービスのProtosフォルダーから内容をgRPCクライアントプロジェクトにコピーする必要があります。つまり、

  • greet.proto

  • customers.proto

  1. ファイルを貼り付けた後、名前空間をクライアントアプリケーションと同じに更新する必要があります。

option csharp_namespace = "GrpcGreeterClient"; 4. GrpcGreeterClient.csprojファイルを更新して、新しく追加した.protoファイルを認識させる必要があります。

<ItemGroup>
    <Protobuf Include="Protos\\greet.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
  <Protobuf Include="Protos\\customers.proto" GrpcServices="Client" />
</ItemGroup>

このProtobuf要素は、コード自動生成機能が.protoファイルを認識する方法です。上記の変更により、クライアントが新しく追加した.protoファイルを使用することを示します。

クライアントをビルドし、すべてが正常にビルドされることを確認します。

dotnet run

次に、コントロールアプリケーションにコードを追加して、サーバー側を呼び出します。Program.csファイルで、次の変更を行う必要があります。

// チャネルを作成します。これはクライアントからサーバーへの接続を表します
// ここで追加するURLは、サーバーのKestrelによって提供されます
var channel = GrcpChannel.ForAddress("<https://localhost:5001>");

// この強い型付けされたクライアントは、.protoファイルを追加したときにコード生成によって作成されます
var client = new Greeter.GreeterClient(channel);

var response = await client.SayHelloAsync(new HelloRequest
{
    Name = "Mohamad"
});

Console.WriteLine("From Server: "  + response.Message);

var customerClient = new Customer.CustomerClient(channel);

var result = await customerClient.GetCustomerInfoAsync(new CustomerFindModel()
{
    UserId = 1
});

Console.WriteLine($"First Name: {result.FirstName} - Last Name: {result.LastName}");

次に、アプリケーションにストリーム処理機能を追加します。

customers.protoファイルに戻り、Customerサービスにストリームメソッドを追加します。

// 消費者(顧客)のリストを返したい
// しかしgRPCではリストを返すことはできず、代わりにストリームを返す必要がある
rpc GetAllCustomers (AllCustomerModel) returns (stream CustomerDataModel);

ご覧のとおり、戻り値にstreamキーワードを追加しました。これは、「複数の」レスポンスからなるストリームを追加していることを意味します。

同時に、空のメッセージも追加する必要があります。

// gRPCでは、空のパラメータを持つメソッドを定義できない
// そのため、空のメッセージを定義する
message AllCustomerModel {

}

このメソッドを実装するには、Servicesフォルダーに移動し、CustomerServiceクラスに次のコードを追加します。

public override async Task GetAllCustomers(AllCustomerModel request, IServerStreamWriter<CustomerDataModel> responseStream, ServerCallContext context)
{
    var allCustomers = new List<CustomerDataModel>();

    var c1 = new CustomerDataModel();
    c1.Name = "Mohamad Lawand";
    c1.Email = "mohamad@mail.com";
    allCustomers.Add(c1);

    var c2 = new CustomerDataModel();
    c2.Name = "Richard Feynman";
    c2.Email = "richard@physics.com";
    allCustomers.Add(c2);

    var c3 = new CustomerDataModel();
    c3.Name = "Bruce Wayne";
    c3.Email = "bruce@gotham.com";
    allCustomers.Add(c3);

    var c4 = new CustomerDataModel();
    c4.Name = "James Bond";
    c4.Email = "007@outlook.com";
    allCustomers.Add(c4);

    foreach(var item in allCustomers)
    {
        await responseStream.WriteAsync(item);
    }
}

次に、サーバー側のcustomers.protoファイルの変更をクライアント側のcustomers.protoファイルにコピーする必要があります。

service Customer {
    rpc GetCustomerInfo (CustomerFindModel) returns (CustomerDataModel);

    // 消費者(顧客)のリストを返したい
    // しかしgRPCではリストを返すことはできず、代わりにストリームを返す必要がある
    rpc GetAllCustomers (AllCustomerModel) returns (stream CustomerDataModel);
}

// gRPCでは、空のパラメータを持つメソッドを定義できない
// そのため、空のメッセージを定義する
message AllCustomerModel {

}

次に、アプリケーションを再度ビルドする必要があります。

dotnet build

次のステップでは、GrpcClientAppのProgram.csファイルを更新して、新しいストリームメソッドを処理します。

var customerCall = customerClient.GetAllCustomers(new AllCustomerModel());

await foreach(var customer in customerCall.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"{customer.Name} {customer.Email}");
}

次に、GrpcGreeterに戻り、greet.protoファイルを更新してストリームメソッドを追加します。

rpc SayHelloStream(HelloRequest) returns (stream HelloReply);

ご覧のとおり、戻り値にstreamキーワードを追加しました。これは、「複数の」レスポンスからなるストリームを追加していることを意味します。このメソッドを実装するには、Servicesフォルダーに移動し、GreeterServiceに次の内容を追加します。

public override async Task SayHelloStream(HelloRequest request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
  for (int i = 0; i < 10; i ++)
  {
    await responseStream.WriteAsync(new HelloReply
    {
      Message = "Hello " + request.Name + " " + i
    });

    await Task.Delay(TimeSpan.FromSeconds(1));
  }
}

次に、greet.protoファイルの変更をサーバー側からクライアント側にコピーし、ビルドします。クライアントアプリケーションのgreet.protoファイルに次の行を追加します。

rpc SayHelloStream(HelloRequest) returns (stream HelloReply);

.protoファイルを保存した後、アプリケーションをビルドすることを確認してください。

dotnet build

これで、Program.csを開き、新しいメソッドを使用できます。

var call = client.SayHelloStream(new HelloRequest
{
    Name = "Mohamad"
});

await foreach(var item in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine("Result " + item.Message);
}

この例は、.NET 5でgRPCクライアント-サーバーアプリケーションを実装する方法を示しています。

まとめ

gRPCがアプリケーション構築において強力であることがわかりますが、その力を発揮するのは容易ではありません。gRPCサービスを構築するには、より多くの準備時間とクライアントとサーバー間の調整が必要だからです。一方、RESTを使用する場合、ほとんど準備を必要とせずにエンドポイントの利用を開始できます。

gRPCはRESTに取って代わるわけではありません。なぜなら、どちらの技術にも特定の適用シナリオがあるからです。ビジネスシナリオと要件に基づいて、プロジェクトに適した技術を選択してください。

著者紹介:

Mohamad Lawandは、金融機関から政府機関まで、さまざまな業界で13年以上の経験を持つ、確固たる先見性のあるテクノロジーアーキテクトです。彼は積極的で適応性が高く、複数のプラットフォームにわたるSaaSおよびブロックチェーン技術に精通しています。Mohamadはまた、自身の知識を共有するYoutubeチャンネルを持っています。

原文リンク:

https://www.infoq.com/articles/getting-started-grpc-dotnet/

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む