gRPC là gì và khác REST API như thế nào?
Protocol Buffers (protobuf) là gì?
Làm sao để tạo gRPC service trong ASP.NET Core?
Unary, Server Streaming, Client Streaming, Bidirectional Streaming là gì?
Khi nào nên dùng gRPC thay vì REST?
┌─────────────────────────────────────────────────────────────────┐
│ gRPC vs REST │
├─────────────────────────────────────────────────────────────────┤
│ │
│ REST: │
│ ┌──────────┐ HTTP/JSON ┌──────────┐ │
│ │ Client │◀───────────────▶│ Server │ │
│ └──────────┘ └──────────┘ │
│ - Text-based (JSON) │
│ - Human readable │
│ - Larger payload size │
│ - Request/Response only │
│ │
│ gRPC: │
│ ┌──────────┐ HTTP/2/Proto ┌──────────┐ │
│ │ Client │◀───────────────▶│ Server │ │
│ └──────────┘ buf └──────────┘ │
│ - Binary (Protocol Buffers) │
│ - Not human readable │
│ - Smaller payload size │
│ - Streaming support │
│ - Strongly typed contracts │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ WHEN TO USE gRPC │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Use gRPC for: │
│ - Microservices communication │
│ - Real-time streaming │
│ - Low latency requirements │
│ - Polyglot environments (multiple languages) │
│ - Internal service-to-service calls │
│ │
│ Use REST for: │
│ - Public APIs │
│ - Browser clients │
│ - Simple CRUD operations │
│ - When human readability matters │
│ - When caching is important │
│ │
└─────────────────────────────────────────────────────────────────┘
// Protos/greet.proto
syntax = "proto3";
option csharp_namespace = "MyGrpcService";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
// Streaming: server sends multiple responses
rpc SayHelloStream (HelloRequest) returns (stream HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greeting.
message HelloReply {
string message = 1;
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
</ItemGroup>
</Project>
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(
HelloRequest request,
ServerCallContext context)
{
_logger.LogInformation("Saying hello to {Name}", request.Name);
return Task.FromResult(new HelloReply
{
Message = $"Hello {request.Name}"
});
}
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<GreeterService>();
app.Run();
service ChatService {
// Server sends multiple messages
rpc Subscribe (SubscribeRequest) returns (stream ChatMessage);
}
message SubscribeRequest {
string channel = 1;
}
message ChatMessage {
string user = 1;
string message = 2;
int64 timestamp = 3;
}
public class ChatService : ChatService.ChatServiceBase
{
private readonly IChannelReader<ChatMessage> _channel;
public ChatService(IChannelReader<ChatMessage> channel)
{
_channel = channel;
}
public override async Task Subscribe(
SubscribeRequest request,
IServerStreamWriter<ChatMessage> responseStream,
ServerCallContext context)
{
await foreach (var message in _channel.ReadAllAsync(context.CancellationToken))
{
if (message.Channel == request.Channel)
{
await responseStream.WriteAsync(message);
}
}
}
}
service UploadService {
// Client sends multiple messages
rpc Upload (stream FileChunk) returns (UploadResponse);
}
message FileChunk {
bytes data = 1;
int32 chunk_number = 2;
}
message UploadResponse {
string file_id = 1;
int64 total_size = 2;
}
public class UploadService : UploadService.UploadServiceBase
{
public override async Task<UploadResponse> Upload(
IAsyncStreamReader<FileChunk> requestStream,
ServerCallContext context)
{
var totalSize = 0L;
var fileId = Guid.NewGuid().ToString();
await foreach (var chunk in requestStream.ReadAllAsync())
{
totalSize += chunk.Data.Length;
// Process chunk
}
return new UploadResponse
{
FileId = fileId,
TotalSize = totalSize
};
}
}
service ChatService {
// Both client and server stream
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
public class ChatService : ChatService.ChatServiceBase
{
public override async Task Chat(
IAsyncStreamReader<ChatMessage> requestStream,
IServerStreamWriter<ChatMessage> responseStream,
ServerCallContext context)
{
// Read from client and broadcast to all
await foreach (var message in requestStream.ReadAllAsync())
{
// Process and broadcast
await responseStream.WriteAsync(new ChatMessage
{
User = "Server",
Message = $"Echo: {message.Message}"
});
}
}
}
dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
// Program.cs
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
// Unary call
var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" });
Console.WriteLine(response.Message);
builder.Services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
});
// Usage
public class MyService
{
private readonly Greeter.GreeterClient _client;
public MyService(Greeter.GreeterClient client)
{
_client = client;
}
public async Task<string> Greet(string name)
{
var response = await _client.SayHelloAsync(
new HelloRequest { Name = name });
return response.Message;
}
}
public class LoggingInterceptor : Interceptor
{
private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
_logger.LogInformation("Starting call {Method}", context.Method);
try
{
return await continuation(request, context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in call {Method}", context.Method);
throw;
}
}
}
// Register
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LoggingInterceptor>();
});
// ✅ Unary for simple request/response
var response = await client.SayHelloAsync(request);
// ✅ Server streaming for real-time updates
await foreach (var message in client.SubscribeAsync(request))
{
Console.WriteLine(message.Message);
}
// ✅ Client streaming for uploads
var uploadResponse = await client.UploadAsync(requestStream);
// ✅ Bidirectional for chat
await foreach (var msg in client.ChatAsync(requestStream))
{
// Process
}
try
{
var response = await client.SayHelloAsync(request);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
Console.WriteLine("Resource not found");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
{
Console.WriteLine("Service unavailable");
}
catch (RpcException ex)
{
Console.WriteLine($"gRPC error: {ex.StatusCode}");
}
// Set deadline for call
var deadline = DateTime.UtcNow.AddSeconds(5);
var response = await client.SayHelloAsync(
request,
deadline: deadline);