dotnet-grpc について
.NET 開発者向けの gRPC ライフサイクルガイドです。.proto の定義やコード生成から ASP.NET Core のサーバー、Grpc.Net.Client の利用まで扱い、ストリーミングや認証なども解説します。
主な機能
- 01.proto 定義とコード生成
- 02ASP.NET Core gRPC サーバーのホスティング
- 03Grpc.Net.Client のクライアントパターン
- 044 種類の gRPC ストリーミング
- 05認証、負荷分散、ヘルスチェック
活用シーン
- 01.NET アプリ向け gRPC 契約の定義と生成
- 02サービス間の双方向ストリーミング実装
- 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/ に配置され、自動的に組み込まれます。
<Protobuf>アイテムを使って.protoファイルをプロジェクトに追加しますGrpcServicesをServer、Client、またはBothに設定します- プロジェクトをビルドします。生成された C# 型とサービススタブは
obj/Debug/net10.0/Protos/に配置されます - 生成された抽象基底クラスを実装するか(サーバー)、生成されたクライアントクラスを使用します
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 相当 | 使用する場合 |
|---|---|---|
OK | 200 | 成功 |
NotFound | 404 | リソースが存在しない |
InvalidArgument | 400 | クライアントが不正なデータを送信した |
PermissionDenied | 403 | 呼び出し元に権限がない |
Unauthenticated | 401 | 有効な認証情報がない |
AlreadyExists | 409 | 重複した作成が試みられた |
ResourceExhausted | 429 | レート制限に達した |
Internal | 500 | 未処理のサーバーエラー |
Unavailable | 503 | 一時的な障害。安全に再試行できます |
DeadlineExceeded | 504 | 操作がタイムアウトした |
詳細なエラー情報
// 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] を参照してください。
エージェントが陥りやすい落とし穴
- リクエストごとに新しい
GrpcChannelを作成しない -- チャネルの作成にはコストがかかり、HTTP/2 接続を管理します。再利用するか、DI で登録したクライアントを使用してください。 <Protobuf>項目のGrpcServicesを省略しない -- 既定値はBothであり、サーバーとクライアントのスタブが両方生成されます。そのため、クライアントプロジェクトには未使用のサーバーコードが、サーバープロジェクトには未使用のクライアントコードが含まれ、プロジェクトが肥大化します。EnableMultipleHttp2Connectionsを有効にせずに、gRPC で L4 ロードバランサーを使用しない -- HTTP/2 の多重化では、単一の接続がすべての RPC を処理するため、負荷分散が機能しなくなります。- gRPC サービスから汎用的な
Exceptionをスローしない -- 適切なStatusCodeと分かりやすいメッセージを指定したRpcExceptionをスローしてください。未処理の例外は、役に立つ詳細情報のないStatusCode.Internalになります。 - クライアントストリームで
CompleteAsync()の呼び出しを忘れない -- サーバーはストリームが完了するまで応答を送信しません。呼び出しを忘れると、呼び出しが停止します。 - ヘルスチェックを登録せずに
grpc.health.v1.Healthを使用しない -- 空のヘルスサービスは常にServingを返すため、ヘルス監視の目的を果たせません。 - CORS を設定せずに gRPC-Web をグローバルに有効にしない -- CORS ポリシーなしで
UseGrpcWeb()を使用すると、あらゆるオリジンから gRPC サービスを呼び出せるようになります。必ず明示的なRequireCors()と組み合わせてください。 - gRPC-Web でクライアントストリーミングや双方向ストリーミングを行おうとしない -- gRPC-Web プロトコルがサポートするのは、単項 RPC とサーバーストリーミングのみです。ブラウザーで全二重通信を行うには、SignalR またはネイティブ gRPC を使用してください。
クレジット
Aaronontheweb/dotnet-skills(MIT ライセンス)を参考にしています。