gRPC và Protocol Buffers trên .NET 10 — Giao tiếp microservices hiệu năng cao

Posted on: 4/18/2026 2:10:16 PM

Trong thế giới microservices, việc các service "nói chuyện" với nhau nhanh và chính xác là yếu tố sống còn. REST với JSON đã phục vụ tốt nhiều năm, nhưng khi hệ thống scale lên hàng trăm service với hàng triệu request/giây, overhead của text-based protocol trở thành nút thắt cổ chai thực sự. gRPC — framework RPC hiệu năng cao của Google dựa trên HTTP/2 và Protocol Buffers — chính là giải pháp mà Netflix, Spotify, Square và hàng nghìn công ty đang sử dụng cho internal communication.

10xNhanh hơn REST cho payload lớn
8,500RPS với 40% CPU (vs REST: 2,200 RPS/65% CPU)
~60%Payload nhỏ hơn so với JSON
4 kiểuCommunication patterns hỗ trợ

1. gRPC là gì và tại sao nên dùng?

gRPC (Google Remote Procedure Call) là một framework RPC mã nguồn mở, sử dụng HTTP/2 làm transport layer và Protocol Buffers (Protobuf) làm Interface Definition Language (IDL) kiêm format serialization. Thay vì gửi JSON text qua HTTP/1.1 như REST, gRPC truyền binary data qua HTTP/2 multiplexed streams — nhanh hơn, nhẹ hơn, và type-safe hơn.

flowchart LR
    A["📱 gRPC Client
.NET / Go / Java"] -->|"HTTP/2 Binary Frame"| B["⚡ gRPC Server
ASP.NET Core"] B -->|"Protobuf Response"| A C["🌐 REST Client
Browser / Mobile"] -->|"HTTP/1.1 JSON Text"| D["🖥️ REST API
ASP.NET Core"] D -->|"JSON Response"| C style A fill:#e94560,stroke:#fff,color:#fff style B fill:#e94560,stroke:#fff,color:#fff style C fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style D fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
gRPC dùng HTTP/2 + Binary so với REST dùng HTTP/1.1 + JSON

Những lợi thế cốt lõi của gRPC:

  • Contract-first API: Định nghĩa API bằng file .proto trước, sau đó tự động sinh code cho cả client và server — đảm bảo type safety xuyên suốt.
  • HTTP/2 multiplexing: Nhiều request/response chạy song song trên cùng một TCP connection, không bị head-of-line blocking như HTTP/1.1.
  • Binary serialization: Protobuf encode data thành binary nhỏ gọn, nhanh hơn JSON parsing đáng kể.
  • Streaming native: Hỗ trợ server streaming, client streaming, và bidirectional streaming — không cần workaround như WebSocket hay SSE.
  • Code generation: Tự động sinh strongly-typed client/server code cho hơn 10 ngôn ngữ từ cùng một file .proto.

2. So sánh gRPC vs REST vs GraphQL

Mỗi protocol có điểm mạnh riêng. Bảng dưới so sánh chi tiết để giúp bạn chọn đúng công cụ cho đúng bài toán:

Tiêu chígRPCRESTGraphQL
TransportHTTP/2HTTP/1.1 hoặc HTTP/2HTTP/1.1 hoặc HTTP/2
Payload formatProtobuf (binary)JSON (text)JSON (text)
Contract.proto file (strict)OpenAPI/Swagger (loose)Schema (strict)
StreamingBi-directional nativeKhông (cần SSE/WebSocket)Subscriptions (cần WebSocket)
Browser supportCần gRPC-Web proxyNativeNative
Latency (P99)~25ms~120ms~95ms
Throughput~8,500 RPS~2,200 RPS~2,800 RPS
Best forService-to-service internalPublic API, CRUDFrontend flexible queries

Kiến trúc hybrid phổ biến nhất

Hầu hết các hệ thống production lớn đều dùng cả hai: REST hoặc GraphQL cho external API (browser, mobile) và gRPC cho internal service-to-service communication. API Gateway đóng vai trò "phiên dịch" giữa hai thế giới.

3. Protocol Buffers — Ngôn ngữ định nghĩa API

Protocol Buffers (Protobuf) là trái tim của gRPC. Bạn định nghĩa cấu trúc dữ liệu và service interface trong file .proto, sau đó compiler protoc tự động sinh code cho ngôn ngữ đích.

3.1. Cú pháp cơ bản

syntax = "proto3"; package ecommerce; option csharp_namespace = "ECommerce.Grpc"; // Message definitions message Product { int32 id = 1; string name = 2; string description = 3; double price = 4; ProductCategory category = 5; repeated string tags = 6; optional string image_url = 7; google.protobuf.Timestamp created_at = 8; } enum ProductCategory { PRODUCT_CATEGORY_UNSPECIFIED = 0; ELECTRONICS = 1; CLOTHING = 2; BOOKS = 3; FOOD = 4; } message ProductFilter { optional ProductCategory category = 1; optional double min_price = 2; optional double max_price = 3; int32 page = 4; int32 page_size = 5; } message ProductList { repeated Product products = 1; int32 total_count = 2; } // Service definition service ProductService { // Unary RPC rpc GetProduct (GetProductRequest) returns (Product); // Server streaming — server gửi nhiều response rpc ListProducts (ProductFilter) returns (stream Product); // Client streaming — client gửi nhiều request rpc BulkCreateProducts (stream Product) returns (BulkCreateResponse); // Bidirectional streaming rpc SyncInventory (stream InventoryUpdate) returns (stream InventoryStatus); } message GetProductRequest { int32 id = 1; } message BulkCreateResponse { int32 created_count = 1; repeated string errors = 2; } message InventoryUpdate { int32 product_id = 1; int32 quantity_change = 2; } message InventoryStatus { int32 product_id = 1; int32 current_stock = 2; bool low_stock_alert = 3; }

Một số quy tắc quan trọng khi viết Protobuf:

  • Field number là vĩnh viễn: Số 1-15 dùng 1 byte encoding, 16-2047 dùng 2 bytes — ưu tiên 1-15 cho các field thường xuyên sử dụng.
  • Không bao giờ reuse field number: Khi xóa field, dùng reserved để "khóa" số đó lại.
  • repeated cho array/list, optional cho nullable field, map<K,V> cho dictionary.
  • Enum phải có giá trị 0: Convention là UNSPECIFIED = 0 để handle default value.

4. Bốn kiểu gRPC Communication

gRPC hỗ trợ 4 kiểu giao tiếp, mỗi kiểu phù hợp với một dạng bài toán khác nhau:

flowchart TB
    subgraph U["1️⃣ Unary RPC"]
        U1["Client"] -->|"1 Request"| U2["Server"]
        U2 -->|"1 Response"| U1
    end

    subgraph SS["2️⃣ Server Streaming"]
        SS1["Client"] -->|"1 Request"| SS2["Server"]
        SS2 -->|"N Responses"| SS1
    end

    subgraph CS["3️⃣ Client Streaming"]
        CS1["Client"] -->|"N Requests"| CS2["Server"]
        CS2 -->|"1 Response"| CS1
    end

    subgraph BD["4️⃣ Bidirectional"]
        BD1["Client"] -->|"N Requests"| BD2["Server"]
        BD2 -->|"M Responses"| BD1
    end

    style U fill:#f8f9fa,stroke:#e94560
    style SS fill:#f8f9fa,stroke:#4CAF50
    style CS fill:#f8f9fa,stroke:#ff9800
    style BD fill:#f8f9fa,stroke:#2c3e50
    style U1 fill:#e94560,stroke:#fff,color:#fff
    style U2 fill:#e94560,stroke:#fff,color:#fff
    style SS1 fill:#4CAF50,stroke:#fff,color:#fff
    style SS2 fill:#4CAF50,stroke:#fff,color:#fff
    style CS1 fill:#ff9800,stroke:#fff,color:#fff
    style CS2 fill:#ff9800,stroke:#fff,color:#fff
    style BD1 fill:#2c3e50,stroke:#fff,color:#fff
    style BD2 fill:#2c3e50,stroke:#fff,color:#fff
Bốn kiểu communication pattern trong gRPC
KiểuMô tảUse case thực tế
Unary1 request → 1 response (giống REST)CRUD operations, authentication, đọc/ghi đơn lẻ
Server Streaming1 request → N responses liên tụcReal-time feed, download file lớn, push notifications
Client StreamingN requests → 1 response tổng hợpUpload file chunked, batch insert, telemetry data
BidirectionalN requests ↔ M responses đồng thờiChat, game state sync, collaborative editing

5. Triển khai gRPC trên .NET 10

5.1. Tạo gRPC Server

Trên .NET 10, gRPC server được tích hợp sẵn trong ASP.NET Core — chỉ cần thêm NuGet package và register service:

// Program.cs — gRPC Server var builder = WebApplication.CreateBuilder(args); // Thêm gRPC services builder.Services.AddGrpc(options => { options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB options.MaxSendMessageSize = 16 * 1024 * 1024; options.EnableDetailedErrors = builder.Environment.IsDevelopment(); }); // Health checks cho gRPC builder.Services.AddGrpcHealthChecks() .AddCheck("database", () => HealthCheckResult.Healthy()); // gRPC Reflection (cho dev tools như grpcurl) builder.Services.AddGrpcReflection(); var app = builder.Build(); // Map gRPC services app.MapGrpcService<ProductServiceImpl>(); app.MapGrpcHealthChecksService(); if (app.Environment.IsDevelopment()) { app.MapGrpcReflectionService(); } app.Run();

5.2. Implement Service

// Services/ProductServiceImpl.cs public class ProductServiceImpl : ProductService.ProductServiceBase { private readonly IProductRepository _repo; private readonly ILogger<ProductServiceImpl> _logger; public ProductServiceImpl( IProductRepository repo, ILogger<ProductServiceImpl> logger) { _repo = repo; _logger = logger; } // Unary RPC public override async Task<Product> GetProduct( GetProductRequest request, ServerCallContext context) { var product = await _repo.GetByIdAsync( request.Id, context.CancellationToken); if (product is null) { throw new RpcException(new Status( StatusCode.NotFound, $"Product {request.Id} not found")); } return MapToProto(product); } // Server Streaming — gửi từng product qua stream public override async Task ListProducts( ProductFilter request, IServerStreamWriter<Product> responseStream, ServerCallContext context) { var products = _repo.GetFilteredAsync(request); await foreach (var product in products .WithCancellation(context.CancellationToken)) { await responseStream.WriteAsync(MapToProto(product)); } } // Client Streaming — nhận batch từ client public override async Task<BulkCreateResponse> BulkCreateProducts( IAsyncStreamReader<Product> requestStream, ServerCallContext context) { var created = 0; var errors = new List<string>(); await foreach (var proto in requestStream .ReadAllAsync(context.CancellationToken)) { try { await _repo.CreateAsync(MapFromProto(proto)); created++; } catch (Exception ex) { errors.Add($"Product '{proto.Name}': {ex.Message}"); } } return new BulkCreateResponse { CreatedCount = created, Errors = { errors } }; } // Bidirectional Streaming — sync inventory realtime public override async Task SyncInventory( IAsyncStreamReader<InventoryUpdate> requestStream, IServerStreamWriter<InventoryStatus> responseStream, ServerCallContext context) { await foreach (var update in requestStream .ReadAllAsync(context.CancellationToken)) { var newStock = await _repo.UpdateStockAsync( update.ProductId, update.QuantityChange); await responseStream.WriteAsync(new InventoryStatus { ProductId = update.ProductId, CurrentStock = newStock, LowStockAlert = newStock < 10 }); } } }

5.3. Tạo gRPC Client

// Client registration trong DI container builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(options => { options.Address = new Uri("https://product-service:5001"); }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan, KeepAlivePingDelay = TimeSpan.FromSeconds(60), KeepAlivePingTimeout = TimeSpan.FromSeconds(30), EnableMultipleHttp2Connections = true }) .AddInterceptor<ClientLoggingInterceptor>(); // Sử dụng client trong service khác public class OrderService { private readonly ProductService.ProductServiceClient _productClient; public OrderService(ProductService.ProductServiceClient productClient) { _productClient = productClient; } public async Task<OrderResult> CreateOrder(OrderRequest order) { // Unary call var product = await _productClient.GetProductAsync( new GetProductRequest { Id = order.ProductId }); // Server streaming — đọc danh sách products var products = new List<Product>(); using var stream = _productClient.ListProducts( new ProductFilter { Category = ProductCategory.Electronics }); await foreach (var p in stream.ResponseStream.ReadAllAsync()) { products.Add(p); } return new OrderResult { /* ... */ }; } }

6. Interceptors — Cross-cutting concerns

Interceptors trong gRPC tương tự middleware trong ASP.NET Core — cho phép bạn xen vào pipeline xử lý request/response để thêm logging, authentication, metrics, retry logic mà không ảnh hưởng business logic.

flowchart LR
    A["Client Request"] --> B["Auth
Interceptor"] B --> C["Logging
Interceptor"] C --> D["Metrics
Interceptor"] D --> E["Service
Handler"] E --> D D --> C C --> B B --> F["Client Response"] style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style B fill:#e94560,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff style D fill:#ff9800,stroke:#fff,color:#fff style E fill:#2c3e50,stroke:#fff,color:#fff style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Pipeline xử lý request qua chuỗi Interceptors

6.1. Server Interceptor — Logging & Metrics

public class ServerLoggingInterceptor : Interceptor { private readonly ILogger<ServerLoggingInterceptor> _logger; public ServerLoggingInterceptor( ILogger<ServerLoggingInterceptor> logger) => _logger = logger; public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>( TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation) { var sw = Stopwatch.StartNew(); var method = context.Method; try { var response = await continuation(request, context); sw.Stop(); _logger.LogInformation( "gRPC {Method} completed in {ElapsedMs}ms", method, sw.ElapsedMilliseconds); return response; } catch (RpcException ex) { sw.Stop(); _logger.LogError(ex, "gRPC {Method} failed with {StatusCode} in {ElapsedMs}ms", method, ex.StatusCode, sw.ElapsedMilliseconds); throw; } } } // Register trong Program.cs builder.Services.AddGrpc(options => { options.Interceptors.Add<ServerLoggingInterceptor>(); });

6.2. Client Interceptor — Retry & Deadline

public class ClientRetryInterceptor : Interceptor { public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) { // Thêm deadline nếu chưa có if (context.Options.Deadline == null) { var options = context.Options .WithDeadline(DateTime.UtcNow.AddSeconds(30)); context = new ClientInterceptorContext<TRequest, TResponse>( context.Method, context.Host, options); } return continuation(request, context); } } // Hoặc dùng built-in retry policy (khuyến nghị) builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o => { o.Address = new Uri("https://product-service:5001"); }) .ConfigureChannel(o => { o.ServiceConfig = new ServiceConfig { MethodConfigs = { new MethodConfig { Names = { MethodName.Default }, RetryPolicy = new RetryPolicy { MaxAttempts = 3, InitialBackoff = TimeSpan.FromMilliseconds(500), MaxBackoff = TimeSpan.FromSeconds(5), BackoffMultiplier = 2, RetryableStatusCodes = { StatusCode.Unavailable, StatusCode.DeadlineExceeded } } } } }; });

7. Health Checks và gRPC Reflection

Trong môi trường production với Kubernetes hoặc load balancer, health checks là bắt buộc. gRPC có chuẩn health checking protocol riêng (grpc.health.v1.Health), và .NET 10 tích hợp sẵn với ASP.NET Core Health Checks system.

// Cấu hình Health Checks builder.Services.AddGrpcHealthChecks() .AddAsyncCheck("database", async ct => { try { await using var conn = new SqlConnection(connectionString); await conn.OpenAsync(ct); return HealthCheckResult.Healthy(); } catch (Exception ex) { return HealthCheckResult.Unhealthy(ex.Message); } }) .AddCheck<RedisHealthCheck>("redis"); // Map endpoint app.MapGrpcHealthChecksService(); // Kubernetes liveness/readiness probe dùng grpc_health_probe: // livenessProbe: // grpc: // port: 5001 // initialDelaySeconds: 5 // periodSeconds: 10

gRPC Reflection — Dev tool không thể thiếu

gRPC Reflection cho phép client khám phá các service và method có sẵn trên server mà không cần file .proto. Kết hợp với grpcurl hoặc grpcui, bạn có thể test gRPC service giống như dùng Postman cho REST. Chỉ nên bật ở môi trường development.

# Cài grpcurl # brew install grpcurl (macOS) hoặc scoop install grpcurl (Windows) # List tất cả services grpcurl -plaintext localhost:5001 list # Describe một service grpcurl -plaintext localhost:5001 describe ecommerce.ProductService # Gọi Unary RPC grpcurl -plaintext -d '{"id": 42}' \ localhost:5001 ecommerce.ProductService/GetProduct # Server streaming grpcurl -plaintext -d '{"category": "ELECTRONICS", "page_size": 10}' \ localhost:5001 ecommerce.ProductService/ListProducts

8. Schema Evolution — Tiến hóa API an toàn

Một trong những thế mạnh lớn nhất của Protobuf là khả năng tiến hóa schema mà không break client/server đang chạy. Đây là yếu tố cực kỳ quan trọng trong microservices khi các service được deploy independent.

flowchart TD
    A["v1: Product có 5 fields"] --> B{"Thêm field mới?"}
    B -->|"✅ An toàn"| C["v2: Thêm optional field
với field number mới"] B -->|"⚠️ Cẩn thận"| D["Đổi type field
cần compatible types"] B -->|"❌ Nguy hiểm"| E["Xóa field hoặc
reuse field number"] C --> F["Old client bỏ qua field mới
New client đọc được field mới"] E --> G["Binary corruption
Data loss"] style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style B fill:#2c3e50,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff style D fill:#ff9800,stroke:#fff,color:#fff style E fill:#e94560,stroke:#fff,color:#fff style F fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Quy tắc tiến hóa schema trong Protocol Buffers

Quy tắc vàng cho Schema Evolution

Hành độngAn toàn?Giải thích
Thêm optional field mới✅ An toànClient cũ bỏ qua, client mới đọc được
Thêm repeated field mới✅ An toànDefault là empty list
Đổi tên field✅ An toànBinary encoding dùng field number, không dùng tên
Xóa field + reserved number⚠️ Cẩn thậnPhải dùng reserved để khóa field number
Đổi int32 → int64⚠️ Tương thíchint32 value vẫn đọc được trong int64
Reuse field number đã xóa❌ KHÔNGGây corrupt data khi client cũ gửi data
Đổi string → int32❌ KHÔNGIncompatible wire types
// Ví dụ schema evolution an toàn message Product { int32 id = 1; string name = 2; // Field 3 đã bị xóa — KHÓA lại reserved 3; reserved "old_description"; double price = 4; ProductCategory category = 5; repeated string tags = 6; optional string image_url = 7; // v2: Thêm fields mới với số mới optional double discount_percent = 8; optional string brand = 9; // v3: Thêm sub-message optional ProductDimensions dimensions = 10; } message ProductDimensions { double weight_kg = 1; double width_cm = 2; double height_cm = 3; double depth_cm = 4; }

Dùng Proto-Break trong CI/CD

Tích hợp tool buf breaking hoặc Proto-Break vào CI pipeline để tự động phát hiện breaking changes trước khi merge. Một breaking change lọt vào production có thể gây cascade failure trên toàn hệ thống microservices.

9. Kiến trúc gRPC trong hệ thống Microservices

Dưới đây là kiến trúc điển hình khi tích hợp gRPC vào hệ thống microservices trên .NET 10:

flowchart TB
    subgraph External["External Clients"]
        Web["🌐 Web App
(Vue.js)"] Mobile["📱 Mobile App"] end subgraph Gateway["API Gateway Layer"] GW["API Gateway
(YARP / Envoy)"] end subgraph Internal["Internal Services (gRPC)"] PS["Product Service
gRPC + .NET 10"] OS["Order Service
gRPC + .NET 10"] IS["Inventory Service
gRPC + .NET 10"] NS["Notification Service
gRPC + .NET 10"] end subgraph Infra["Infrastructure"] DB1[("PostgreSQL")] DB2[("PostgreSQL")] MQ["Message Broker"] end Web -->|"REST / GraphQL"| GW Mobile -->|"REST / GraphQL"| GW GW -->|"gRPC"| PS GW -->|"gRPC"| OS OS -->|"gRPC"| PS OS -->|"gRPC"| IS OS -->|"gRPC"| NS IS -->|"Bidirectional
Streaming"| PS PS --> DB1 OS --> DB2 IS --> DB1 NS --> MQ style Web fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style Mobile fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style GW fill:#e94560,stroke:#fff,color:#fff style PS fill:#2c3e50,stroke:#fff,color:#fff style OS fill:#2c3e50,stroke:#fff,color:#fff style IS fill:#2c3e50,stroke:#fff,color:#fff style NS fill:#2c3e50,stroke:#fff,color:#fff style DB1 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style DB2 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style MQ fill:#f8f9fa,stroke:#ff9800,color:#2c3e50
Kiến trúc microservices với gRPC cho internal communication

10. Performance Tuning và Best Practices

10.1. Connection Management

// Tối ưu connection pool builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o => { o.Address = new Uri("https://product-service:5001"); }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { // Giữ connection alive — tránh TCP handshake lặp lại PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan, // HTTP/2 keep-alive ping KeepAlivePingDelay = TimeSpan.FromSeconds(60), KeepAlivePingTimeout = TimeSpan.FromSeconds(30), // Cho phép nhiều HTTP/2 connection khi cần EnableMultipleHttp2Connections = true, // Connection lifetime — rotate để load balancing PooledConnectionLifetime = TimeSpan.FromMinutes(5) });

10.2. Compression

// Server-side: enable Gzip compression builder.Services.AddGrpc(options => { options.ResponseCompressionAlgorithm = "gzip"; options.ResponseCompressionLevel = CompressionLevel.Optimal; options.CompressionProviders = new List<ICompressionProvider> { new GzipCompressionProvider(CompressionLevel.Optimal) }; }); // Client-side: gửi request với compression var callOptions = new CallOptions( writeOptions: new WriteOptions(WriteFlags.NoCompress)); // disable // hoặc enable bằng channel option var channel = GrpcChannel.ForAddress("https://server:5001", new GrpcChannelOptions { CompressionProviders = new[] { new GzipCompressionProvider(CompressionLevel.Fastest) } });

10.3. Deadline & Cancellation

Luôn set deadline cho mọi gRPC call

Không set deadline = chờ vô hạn. Trong microservices, một service bị treo sẽ kéo theo toàn bộ call chain bị block. Quy tắc: mỗi RPC call phải có deadline, và deadline phải được propagate qua các service downstream.

// Set deadline khi gọi var deadline = DateTime.UtcNow.AddSeconds(5); var product = await client.GetProductAsync( new GetProductRequest { Id = 42 }, deadline: deadline); // Propagate deadline trong server public override async Task<OrderResult> CreateOrder( CreateOrderRequest request, ServerCallContext context) { // Dùng deadline từ caller, không tạo deadline mới dài hơn var remainingTime = context.Deadline - DateTime.UtcNow; var childDeadline = DateTime.UtcNow.Add(remainingTime * 0.8); var product = await _productClient.GetProductAsync( new GetProductRequest { Id = request.ProductId }, deadline: childDeadline); // ... }

10.4. Checklist Best Practices

PracticeMô tảTầm quan trọng
Luôn set deadlineMọi RPC call cần timeout rõ ràng🔴 Bắt buộc
Dùng CancellationTokenTruyền token qua toàn bộ async chain🔴 Bắt buộc
Retry policyRetry cho Unavailable, DeadlineExceeded🔴 Bắt buộc
Health checksImplement grpc.health.v1 cho K8s probes🔴 Bắt buộc
Interceptors cho loggingLog method, duration, status code🟡 Khuyến nghị
CompressionGzip cho payload > 1KB🟡 Khuyến nghị
Connection poolingReuse connection, enable multiplexing🟡 Khuyến nghị
Buf/Proto-Break CIDetect breaking schema changes🟡 Khuyến nghị
gRPC-Web proxyNếu cần browser access qua Envoy🟢 Tùy chọn
Reflection chỉ ở devTắt reflection ở production🟢 Tùy chọn

11. gRPC-Web — Khi cần gọi từ Browser

gRPC chuẩn không hoạt động trực tiếp trên browser vì browser không expose HTTP/2 framing ở tầng ứng dụng. gRPC-Web giải quyết vấn đề này bằng cách encode gRPC frames trong HTTP/1.1 hoặc HTTP/2 request thông thường.

// Server: enable gRPC-Web builder.Services.AddGrpc(); var app = builder.Build(); app.UseGrpcWeb(); // Middleware chuyển đổi gRPC-Web ↔ gRPC app.MapGrpcService<ProductServiceImpl>() .EnableGrpcWeb(); // Enable cho từng service // Client JavaScript/TypeScript (với @grpc/grpc-js hoặc connect-web) // Hoặc dùng Envoy proxy để translate gRPC-Web → gRPC

Connect Protocol — Alternative hiện đại

Nếu bạn cần browser compatibility tốt hơn, hãy xem xét Connect (connectrpc.com) — một protocol tương thích gRPC nhưng cũng hỗ trợ JSON over HTTP/1.1, giúp debug dễ hơn và hoạt động với mọi HTTP client mà không cần proxy đặc biệt.

12. Khi nào KHÔNG nên dùng gRPC?

gRPC không phải silver bullet. Dưới đây là những trường hợp bạn nên cân nhắc protocol khác:

  • Public API cho third-party: REST với OpenAPI documentation dễ tiếp cận hơn nhiều — developer chỉ cần curl hoặc Postman.
  • Browser-to-server đơn giản: Nếu không cần streaming, REST/GraphQL đơn giản hơn gRPC-Web + proxy.
  • Team nhỏ, ít service: Overhead của Protobuf toolchain không worth it nếu chỉ có 2-3 services.
  • Cần HTTP caching: gRPC không tận dụng được HTTP cache (CDN, reverse proxy) vì dùng POST cho mọi request.
  • Payload chủ yếu là text/human-readable: Nếu cần inspect request/response thường xuyên, JSON dễ debug hơn binary.

Kết luận

gRPC trên .NET 10 mang lại sự kết hợp hoàn hảo giữa hiệu năng (binary serialization, HTTP/2 multiplexing), type safety (Protobuf contract), và developer experience (code generation, interceptors, health checks tích hợp sẵn). Với internal microservices communication — nơi mà latency thấp và throughput cao là yêu cầu then chốt — gRPC là lựa chọn tối ưu nhất hiện tại.

Bắt đầu bằng cách định nghĩa .proto file cho service quan trọng nhất, triển khai Unary RPC trước, sau đó mở rộng sang streaming khi có nhu cầu thực tế. Nhớ luôn set deadline, implement health checks, và thiết lập schema evolution rules ngay từ đầu — đó là nền tảng để hệ thống gRPC của bạn scale một cách bền vững.

Nguồn tham khảo