mcpField
D
dotnet-grpc

dotnet-grpc

github.com/wshaddix開発8675 回インストール35 週間前に更新
リポジトリ

dotnet-grpc について

.NET 開発者向けの gRPC ライフサイクルガイドです。.proto の定義やコード生成から ASP.NET Core のサーバー、Grpc.Net.Client の利用まで扱い、ストリーミングや認証なども解説します。

主な機能

  1. 01.proto 定義とコード生成
  2. 02ASP.NET Core gRPC サーバーのホスティング
  3. 03Grpc.Net.Client のクライアントパターン
  4. 044 種類の gRPC ストリーミング
  5. 05認証、負荷分散、ヘルスチェック

活用シーン

  1. 01.NET アプリ向け gRPC 契約の定義と生成
  2. 02サービス間の双方向ストリーミング実装
  3. 03gRPC の認証とヘルスプローブの設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-grpc

インストール後、Claude Code で/dotnet-grpcを入力して読み込まれたか確認します。

詳細

dotnet-grpc

.NET アプリケーションにおける gRPC のライフサイクル全体を扱います。.proto によるサービス定義、コード生成、ASP.NET Core gRPC サーバーの実装とエンドポイントのホスティング、Grpc.Net.Client のクライアントパターン、4 種類すべてのストリーミングパターン(単項、サーバーストリーミング、クライアントストリーミング、双方向ストリーミング)、認証、負荷分散、ヘルスチェックを網羅します。

対象外: ソースジェネレーターの作成パターン(インクリメンタルジェネレーター API、Roslyn 構文ツリー)については、[skill:dotnet-csharp-source-generators] を参照してください。HTTP クライアントファクトリーのパターンと回復性パイプラインの構成については、[skill:dotnet-http-client] と [skill:dotnet-resilience] を参照してください。ネイティブ AOT のアーキテクチャとトリミング戦略については、AOT コンパイルは [skill:dotnet-native-aot]、AOT を前提とした設計パターンは [skill:dotnet-aot-architecture]、トリミングに安全な開発は [skill:dotnet-trimming] を参照してください。

相互参照:[skill:dotnet-resilience] では gRPC チャネルの再試行/サーキットブレーカーについて、[skill:dotnet-serialization] では Protobuf ワイヤーフォーマットの詳細について説明しています。gRPC サービスのテストについては、[skill:dotnet-integration-testing] を参照してください。


Proto の定義とコード生成

プロジェクトのセットアップ

gRPC では、インターフェース定義言語として Protocol Buffers を使用します。Grpc.Tools パッケージは、ビルド時に .proto ファイルから C# コードを生成します。

サーバープロジェクト:

<ItemGroup>
  <PackageReference Include="Grpc.AspNetCore" Version="2.*" />
</ItemGroup>

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

クライアントプロジェクト:

<ItemGroup>
  <PackageReference Include="Google.Protobuf" Version="3.*" />
  <PackageReference Include="Grpc.Net.Client" Version="2.*" />
  <PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="All" />
</ItemGroup>

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

共有コントラクトプロジェクト(大規模なサービスでは推奨):

<ItemGroup>
  <PackageReference Include="Google.Protobuf" Version="3.*" />
  <PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
  <Protobuf Include="Protos\*.proto" GrpcServices="Both" />
</ItemGroup>
Proto ファイルの定義
syntax = "proto3";

option csharp_namespace = "MyApp.Grpc";

package myapp;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

// Service definition with all 4 streaming patterns
service OrderService {
  // Unary: single request, single response
  rpc GetOrder (GetOrderRequest) returns (OrderResponse);

  // Server streaming: single request, stream of responses
  rpc ListOrders (ListOrdersRequest) returns (stream OrderResponse);

  // Client streaming: stream of requests, single response
  rpc UploadOrders (stream CreateOrderRequest) returns (UploadOrdersResponse);

  // Bidirectional streaming: stream of requests, stream of responses
  rpc ProcessOrders (stream CreateOrderRequest) returns (stream OrderResponse);
}

message GetOrderRequest {
  int32 id = 1;
}

message ListOrdersRequest {
  string customer_id = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItemMessage items = 2;
}

message OrderResponse {
  int32 id = 1;
  string customer_id = 2;
  repeated OrderItemMessage items = 3;
  google.protobuf.Timestamp created_at = 4;
}

message OrderItemMessage {
  string product_id = 1;
  int32 quantity = 2;
  double unit_price = 3;
}

message UploadOrdersResponse {
  int32 orders_created = 1;
}
コード生成のワークフロー

Grpc.Tools パッケージは、ビルド時に Protobuf コンパイラー(protoc)と C# gRPC プラグインを実行します。生成されたファイルは obj/ に配置され、自動的に組み込まれます。

  1. <Protobuf> アイテムを使って .proto ファイルをプロジェクトに追加します
  2. GrpcServices を Server、Client、または Both に設定します
  3. プロジェクトをビルドします。生成された C# 型とサービススタブは obj/Debug/net10.0/Protos/ に配置されます
  4. 生成された抽象基底クラスを実装するか(サーバー)、生成されたクライアントクラスを使用します

gRPC のコード生成ツールチェーンは、ソース生成を使用して .proto 定義から C# スタブを生成します。概念的には [skill:dotnet-csharp-source-generators] と似ていますが、Roslyn のインクリメンタルジェネレーターではなく protoc を使用します。


ASP.NET Core gRPC サーバー

サービスの実装

生成された抽象基底クラスを実装します。

using Grpc.Core;
using MyApp.Grpc;

public sealed class OrderGrpcService(
    OrderRepository repository,
    ILogger<OrderGrpcService> logger) : OrderService.OrderServiceBase
{
    // Unary
    public override async Task<OrderResponse> GetOrder(
        GetOrderRequest request,
        ServerCallContext context)
    {
        var order = await repository.GetByIdAsync(request.Id, context.CancellationToken);
        if (order is null)
        {
            throw new RpcException(new Status(StatusCode.NotFound,
                $"Order {request.Id} not found"));
        }

        return MapToResponse(order);
    }

    // Server streaming
    public override async Task ListOrders(
        ListOrdersRequest request,
        IServerStreamWriter<OrderResponse> responseStream,
        ServerCallContext context)
    {
        await foreach (var order in repository.ListByCustomerAsync(
            request.CustomerId, context.CancellationToken))
        {
            await responseStream.WriteAsync(MapToResponse(order),
                context.CancellationToken);
        }
    }

    // Client streaming
    public override async Task<UploadOrdersResponse> UploadOrders(
        IAsyncStreamReader<CreateOrderRequest> requestStream,
        ServerCallContext context)
    {
        var count = 0;
        await foreach (var request in requestStream.ReadAllAsync(
            context.CancellationToken))
        {
            await repository.CreateAsync(MapFromRequest(request),
                context.CancellationToken);
            count++;
        }

        return new UploadOrdersResponse { OrdersCreated = count };
    }

    // Bidirectional streaming
    public override async Task ProcessOrders(
        IAsyncStreamReader<CreateOrderRequest> requestStream,
        IServerStreamWriter<OrderResponse> responseStream,
        ServerCallContext context)
    {
        await foreach (var request in requestStream.ReadAllAsync(
            context.CancellationToken))
        {
            var order = await repository.CreateAsync(MapFromRequest(request),
                context.CancellationToken);
            await responseStream.WriteAsync(MapToResponse(order),
                context.CancellationToken);
        }
    }

    private static OrderResponse MapToResponse(Order order) =>
        new()
        {
            Id = order.Id,
            CustomerId = order.CustomerId,
            CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(
                order.CreatedAt)
        };

    private static Order MapFromRequest(CreateOrderRequest request) =>
        new()
        {
            CustomerId = request.CustomerId,
            Items = request.Items.Select(i => new OrderItem
            {
                ProductId = i.ProductId,
                Quantity = i.Quantity,
                UnitPrice = (decimal)i.UnitPrice
            }).ToList()
        };
}
エンドポイントのホスティング

ASP.NET Core パイプラインに gRPC サービスを登録します。

var builder = WebApplication.CreateBuilder(args);

// Add gRPC services
builder.Services.AddGrpc(options =>
{
    options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
    options.MaxSendMessageSize = 4 * 1024 * 1024;
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
});

var app = builder.Build();

// Map gRPC service endpoints
app.MapGrpcService<OrderGrpcService>();

app.Run();
gRPC リフレクション(開発時)

grpcurl や grpcui などのツールで使用できるよう、gRPC リフレクションを有効にします。

builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();

var app = builder.Build();

app.MapGrpcService<OrderGrpcService>();

if (app.Environment.IsDevelopment())
{
    app.MapGrpcReflectionService();
}

Grpc.Net.Client を使ったクライアントパターン

基本的なクライアント
using Grpc.Net.Client;
using MyApp.Grpc;

// Create a channel (reuse across calls -- channels are expensive to create)
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new OrderService.OrderServiceClient(channel);

// Unary call
var response = await client.GetOrderAsync(
    new GetOrderRequest { Id = 42 });
IHttpClientFactory に登録したクライアント

接続プールと回復性を利用するため、IHttpClientFactory 経由で gRPC クライアントを登録します。

builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(options =>
    {
        options.Address = new Uri("https://order-service:5001");
    })
    .ConfigureChannel(options =>
    {
        options.MaxReceiveMessageSize = 4 * 1024 * 1024;
    });

[skill:dotnet-resilience] を使って回復性を適用します。

builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(options =>
    {
        options.Address = new Uri("https://order-service:5001");
    })
    .AddStandardResilienceHandler();
サーバーストリームの読み取り
using var call = client.ListOrders(
    new ListOrdersRequest { CustomerId = "cust-123" });

await foreach (var order in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"Order {order.Id}: {order.CustomerId}");
}
クライアントストリーミング
using var call = client.UploadOrders();

foreach (var order in ordersToCreate)
{
    await call.RequestStream.WriteAsync(new CreateOrderRequest
    {
        CustomerId = order.CustomerId
    });
}

// Signal completion
await call.RequestStream.CompleteAsync();

// Read the response
var response = await call;
Console.WriteLine($"Created {response.OrdersCreated} orders");
双方向ストリーミング
using var call = client.ProcessOrders();

// Start reading responses in background
var readTask = Task.Run(async () =>
{
    await foreach (var response in call.ResponseStream.ReadAllAsync())
    {
        Console.WriteLine($"Processed order {response.Id}");
    }
});

// Send requests
foreach (var order in ordersToProcess)
{
    await call.RequestStream.WriteAsync(new CreateOrderRequest
    {
        CustomerId = order.CustomerId
    });
}

await call.RequestStream.CompleteAsync();
await readTask;

ストリーミングパターンの概要

gRPC は、次の 4 種類の通信パターンをサポートしています。

パターンリクエストレスポンス用途
単項単一メッセージ単一メッセージ標準的なリクエスト/レスポンス(CRUD、クエリ)
サーバーストリーミング単一メッセージメッセージのストリームリアルタイムフィード、大量の結果セット、プッシュ通知
クライアントストリーミングメッセージのストリーム単一メッセージ一括アップロード、集約、テレメトリの取り込み
双方向ストリーミングメッセージのストリームメッセージのストリームチャット、リアルタイムコラボレーション、イベント処理

認証

ベアラートークン(JWT)

サーバー側の認証:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://identity.example.com";
        options.TokenValidationParameters.ValidAudience = "order-api";
    });

builder.Services.AddAuthorization();
builder.Services.AddGrpc();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGrpcService<OrderGrpcService>().RequireAuthorization();

クライアント側でのトークンの伝播:

builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(options =>
    {
        options.Address = new Uri("https://order-service:5001");
    })
    .AddCallCredentials(async (context, metadata, serviceProvider) =>
    {
        var tokenProvider = serviceProvider.GetRequiredService<ITokenProvider>();
        var token = await tokenProvider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    });
証明書認証(mTLS)

相互 TLS を使用したサービス間認証:

// Server: require client certificates
builder.WebHost.ConfigureKestrel(kestrel =>
{
    kestrel.ConfigureHttpsDefaults(https =>
    {
        https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
    });
});

builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.Chained;
        options.RevocationMode = X509RevocationMode.NoCheck; // Configure per environment
    });
// Client: provide client certificate
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(
    new X509Certificate2("client.pfx", "password"));

using var channel = GrpcChannel.ForAddress("https://order-service:5001",
    new GrpcChannelOptions
    {
        HttpHandler = handler
    });

負荷分散

クライアント側の負荷分散

gRPC は、サービスディスカバリーを使ったクライアント側の負荷分散をサポートしています。

// DNS-based service discovery with round-robin
builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(options =>
    {
        options.Address = new Uri("dns:///order-service:5001");
    })
    .ConfigureChannel(options =>
    {
        options.Credentials = ChannelCredentials.Insecure;
        options.ServiceConfig = new ServiceConfig
        {
            LoadBalancingConfigs = { new RoundRobinConfig() }
        };
    });
プロキシベースのロードバランシング

ロードバランサー(Kubernetes、Envoy、YARP など)を使用する環境では、次の点に注意してください。

  • L7(HTTP/2 対応)ロードバランサーを使用します。L4 ロードバランサーは TCP レベルでルーティングするため、HTTP/2 では単一の接続上で多重化されるすべての gRPC リクエストが、1 つのバックエンドに固定されます。
  • Envoy、Linkerd、gRPC をサポートする Kubernetes のイングレスコントローラーは、RPC レベルでリクエストを分散します。
  • プロキシの背後で複数の接続を許可するには、SocketsHttpHandler.EnableMultipleHttp2Connections = true を設定します。
builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(options =>
    {
        options.Address = new Uri("https://order-service-lb:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        EnableMultipleHttp2Connections = true
    });

ヘルスチェック

gRPC ヘルスチェックプロトコル

オーケストレーターやロードバランサーがサービスのステータスを確認できるように、標準の gRPC ヘルスチェックプロトコル(grpc.health.v1.Health)を実装します。

builder.Services.AddGrpc();
builder.Services.AddGrpcHealthChecks()
    .AddCheck("database", () =>
    {
        // Custom health check logic
        return HealthCheckResult.Healthy();
    });

var app = builder.Build();

app.MapGrpcService<OrderGrpcService>();
app.MapGrpcHealthChecksService();
ASP.NET Core ヘルスチェックとの統合

gRPC ヘルスチェックは、標準の ASP.NET Core ヘルスチェックシステムと統合できます。

builder.Services.AddHealthChecks()
    .AddNpgSql(
        builder.Configuration.GetConnectionString("OrderDb")!,
        name: "order-db",
        tags: ["ready"]);

builder.Services.AddGrpc();
builder.Services.AddGrpcHealthChecks()
    .AddAsyncCheck("order-db", async (sp, ct) =>
    {
        var healthCheckService = sp.GetRequiredService<HealthCheckService>();
        var report = await healthCheckService.CheckHealthAsync(
            r => r.Tags.Contains("ready"), ct);
        return report.Status == HealthStatus.Healthy
            ? HealthCheckResult.Healthy()
            : HealthCheckResult.Unhealthy();
    });
gRPC 用 Kubernetes プローブ
# Use grpc health check probe (Kubernetes 1.24+)
livenessProbe:
  grpc:
    port: 5001
  initialDelaySeconds: 10
  periodSeconds: 15

readinessProbe:
  grpc:
    port: 5001
  initialDelaySeconds: 5
  periodSeconds: 10

インターセプター

gRPC インターセプターは、ASP.NET Core ミドルウェアや HTTP DelegatingHandlers に相当する、gRPC 呼び出し用のミドルウェアです。

サーバーインターセプター
public sealed class LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    : Interceptor
{
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            var response = await continuation(request, context);
            logger.LogInformation(
                "gRPC {Method} completed in {ElapsedMs}ms",
                context.Method, stopwatch.ElapsedMilliseconds);
            return response;
        }
        catch (RpcException ex)
        {
            logger.LogError(ex,
                "gRPC {Method} failed with {StatusCode}",
                context.Method, ex.StatusCode);
            throw;
        }
    }
}

// Register
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<LoggingInterceptor>();
});
クライアントインターセプター
public sealed class AuthInterceptor(ITokenProvider tokenProvider) : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var token = tokenProvider.GetCachedToken();
        var headers = context.Options.Headers ?? new Metadata();
        headers.Add("Authorization", $"Bearer {token}");

        var newContext = new ClientInterceptorContext<TRequest, TResponse>(
            context.Method, context.Host,
            context.Options.WithHeaders(headers));

        return continuation(request, newContext);
    }
}

エラー処理

ステータスコード

ドメインエラーを gRPC ステータスコードにマッピングします。

gRPC ステータスHTTP 相当使用する場合
OK200成功
NotFound404リソースが存在しない
InvalidArgument400クライアントが不正なデータを送信した
PermissionDenied403呼び出し元に権限がない
Unauthenticated401有効な認証情報がない
AlreadyExists409重複した作成が試みられた
ResourceExhausted429レート制限に達した
Internal500未処理のサーバーエラー
Unavailable503一時的な障害。安全に再試行できます
DeadlineExceeded504操作がタイムアウトした
詳細なエラー情報
// Server: throw with metadata
var status = new Status(StatusCode.InvalidArgument, "Validation failed");
var metadata = new Metadata
{
    { "field", "customer_id" },
    { "reason", "Customer ID is required" }
};
throw new RpcException(status, metadata);

// Client: read error metadata
try
{
    var response = await client.GetOrderAsync(request);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
{
    var field = ex.Trailers.GetValue("field");
    var reason = ex.Trailers.GetValue("reason");
    logger.LogWarning("Validation error on {Field}: {Reason}", field, reason);
}

デッドラインとキャンセル

無期限の待機を防ぐため、gRPC 呼び出しには必ずデッドラインを設定します。

// Client: set a deadline
var deadline = DateTime.UtcNow.AddSeconds(10);
var response = await client.GetOrderAsync(
    new GetOrderRequest { Id = 42 },
    deadline: deadline);

// Server: check deadline and propagate cancellation
public override async Task<OrderResponse> GetOrder(
    GetOrderRequest request,
    ServerCallContext context)
{
    // context.CancellationToken is automatically cancelled when deadline expires
    var order = await repository.GetByIdAsync(request.Id, context.CancellationToken);
    // ...
}

ブラウザクライアント向け gRPC-Web

ブラウザは、ネイティブ gRPC に必要な HTTP/2 トレーラーをサポートしていません。gRPC-Web は、トレーラーなしで HTTP/1.1 と HTTP/2 上で動作するプロトコルのバリアントです。これにより、ブラウザの JavaScript クライアントから gRPC サービスを呼び出せます。

サーバーの構成
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();
builder.Services.AddCors(options =>
{
    options.AddPolicy("GrpcWeb", policy =>
    {
        policy.WithOrigins("https://app.example.com")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding");
    });
});

var app = builder.Build();

app.UseRouting();
app.UseCors();
app.UseGrpcWeb(); // Must be between UseRouting and MapGrpcService

app.MapGrpcService<OrderGrpcService>()
    .EnableGrpcWeb()
    .RequireCors("GrpcWeb");
JavaScript クライアント(grpc-web)
// Using @improbable-eng/grpc-web or grpc-web package
import { OrderServiceClient } from './generated/order_grpc_web_pb';
import { GetOrderRequest } from './generated/order_pb';

const client = new OrderServiceClient('https://api.example.com');

const request = new GetOrderRequest();
request.setId(42);

client.getOrder(request, {}, (err, response) => {
    if (err) {
        console.error('gRPC error:', err.message);
        return;
    }
    console.log('Order:', response.toObject());
});
Envoy プロキシを使う方法

ASP.NET Core の gRPC-Web ミドルウェアの代わりに、Envoy プロキシを使って gRPC-Web リクエストをネイティブ gRPC に変換できます。これは、gRPC サービスを変更できない場合に便利です。

# Envoy filter configuration
http_filters:
  - name: envoy.filters.http.grpc_web
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
  - name: envoy.filters.http.cors
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
  - name: envoy.filters.http.router
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
gRPC-Web の制限事項
  • 単項呼び出しとサーバーストリーミングのみ — gRPC-Web はクライアントストリーミングと双方向ストリーミングをサポートしていません
  • HTTP/2 トレーラーなし — ステータスと末尾のメタデータはレスポンス本文にエンコードされます
  • CORS が必要 — クロスオリジンリクエストには、サーバーで明示的な CORS の構成が必要です
  • ブラウザで全二重通信を行う場合は SignalR を検討します — 双方向ストリーミングが必要な場合の代替手段については、[skill:dotnet-realtime-communication] を参照してください

主要な原則

  • .proto ファイルを契約として使用する -- クライアントとサーバーで共有される、API の構造に関する唯一の信頼できる情報源です
  • <Protobuf> 項目に GrpcServices を設定する -- サービスプロジェクトでは Server、利用側のプロジェクトでは Client、共有契約では Both を指定します
  • チャネルを再利用する -- GrpcChannel は HTTP/2 接続を管理します。呼び出しごとに新しいチャネルを作成すると、リソースを浪費します
  • DI を使って gRPC クライアントを登録する -- AddGrpcClient は IHttpClientFactory と連携し、コネクションプーリングと回復性を実現します
  • 必ずデッドラインを設定する -- デッドラインを設定しない呼び出しは、サーバーの応答が遅い場合や到達不能な場合に、無期限に停止する可能性があります
  • L7 ロードバランサーを使用する -- HTTP/2 は単一の TCP 接続上で多重化するため、L4 ロードバランサーではすべてのトラフィックが 1 つのバックエンドに固定されます
  • gRPC ヘルスチェックプロトコルを実装する -- Kubernetes のプローブやロードバランサーで、サービスの状態を監視できるようになります
  • ブラウザーのクライアントには gRPC-Web を使用する -- ネイティブ gRPC ではブラウザーがサポートしていない HTTP/2 トレーラーが必要です。gRPC-Web はこの問題を解決します

事前コンパイルによる gRPC サービスのビルド時に、Native AOT コンパイルパイプラインについては [skill:dotnet-native-aot] を、AOT 互換のパターンについては [skill:dotnet-aot-architecture] を参照してください。


エージェントが陥りやすい落とし穴

  1. リクエストごとに新しい GrpcChannel を作成しない -- チャネルの作成にはコストがかかり、HTTP/2 接続を管理します。再利用するか、DI で登録したクライアントを使用してください。
  2. <Protobuf> 項目の GrpcServices を省略しない -- 既定値は Both であり、サーバーとクライアントのスタブが両方生成されます。そのため、クライアントプロジェクトには未使用のサーバーコードが、サーバープロジェクトには未使用のクライアントコードが含まれ、プロジェクトが肥大化します。
  3. EnableMultipleHttp2Connections を有効にせずに、gRPC で L4 ロードバランサーを使用しない -- HTTP/2 の多重化では、単一の接続がすべての RPC を処理するため、負荷分散が機能しなくなります。
  4. gRPC サービスから汎用的な Exception をスローしない -- 適切な StatusCode と分かりやすいメッセージを指定した RpcException をスローしてください。未処理の例外は、役に立つ詳細情報のない StatusCode.Internal になります。
  5. クライアントストリームで CompleteAsync() の呼び出しを忘れない -- サーバーはストリームが完了するまで応答を送信しません。呼び出しを忘れると、呼び出しが停止します。
  6. ヘルスチェックを登録せずに grpc.health.v1.Health を使用しない -- 空のヘルスサービスは常に Serving を返すため、ヘルス監視の目的を果たせません。
  7. CORS を設定せずに gRPC-Web をグローバルに有効にしない -- CORS ポリシーなしで UseGrpcWeb() を使用すると、あらゆるオリジンから gRPC サービスを呼び出せるようになります。必ず明示的な RequireCors() と組み合わせてください。
  8. gRPC-Web でクライアントストリーミングや双方向ストリーミングを行おうとしない -- gRPC-Web プロトコルがサポートするのは、単項 RPC とサーバーストリーミングのみです。ブラウザーで全二重通信を行うには、SignalR またはネイティブ gRPC を使用してください。

クレジット

Aaronontheweb/dotnet-skills(MIT ライセンス)を参考にしています。


参考資料

コメント