Performance First
gRPC is 5-10x faster than REST due to binary encoding and HTTP/2. Perfect for microservices.
gRPC (gRPC Remote Procedure Calls) is a high-performance, open-source RPC framework developed by Google. It enables you to call remote functions as if they were local functions, abstracting away the complexity of network communication.
gRPC uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport. This combination provides significant performance advantages over traditional REST APIs using JSON over HTTP/1.1. The framework generates client and server code from .proto files, ensuring type safety and reducing boilerplate code.
Key Insight: gRPC is designed for inter-service communication in microservices architectures. It’s optimized for performance, with binary serialization reducing payload sizes by 50-70% compared to JSON, and HTTP/2 providing multiplexing and header compression.
| Feature | REST/JSON | gRPC |
|---|---|---|
| Speed | Slower (text parsing) | Faster (binary) |
| Payload Size | Larger (text) | Smaller (binary) |
| Streaming | Limited | Full support |
| Type Safety | Runtime | Compile-time |
| Code Generation | Manual | Automatic |
| Browser Support | Excellent | Limited |
Protocol Buffers (protobuf) is a binary serialization format. Think of it as a more efficient alternative to JSON.
JSON (text-based):
{ "id": 12345, "name": "John Doe", "age": 30}Size: ~80 bytes
Protocol Buffers (binary):
[encoded binary data]Size: ~25 bytes (3x smaller!)
.proto files define your service contract. They’re like API documentation + code generator input.
syntax = "proto3";
package user_service;
// Message definition (like a struct/class)message User { int32 id = 1; string name = 2; string email = 3; int32 age = 4;}
message GetUserRequest { int32 user_id = 1;}
message GetUserResponse { User user = 1;}
// Service definitionservice UserService { // Unary RPC: one request, one response rpc GetUser(GetUserRequest) returns (GetUserResponse);
// Server streaming: one request, multiple responses rpc ListUsers(GetUserRequest) returns (stream User);
// Client streaming: multiple requests, one response rpc CreateUsers(stream User) returns (CreateUsersResponse);
// Bidirectional streaming: multiple requests, multiple responses rpc ChatUsers(stream ChatMessage) returns (stream ChatMessage);}Field numbers (1, 2, 3…) are important:
Common types:
int32, int64 - Integersfloat, double - Floating pointbool - Booleanstring - UTF-8 stringbytes - Raw bytesrepeated - Arrays/listsmap - Key-value pairsOne request, one response. Like a function call.
service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse);}Flow:
Client → Request → ServerClient ← Response ← ServerOne request, multiple responses. Server sends stream of data.
service UserService { rpc ListUsers(GetUserRequest) returns (stream User);}Flow:
Client → Request → ServerClient ← User 1 ← ServerClient ← User 2 ← ServerClient ← User 3 ← Server...Use cases:
Multiple requests, one response. Client sends stream of data.
service UserService { rpc CreateUsers(stream User) returns (CreateUsersResponse);}Flow:
Client → User 1 → ServerClient → User 2 → ServerClient → User 3 → ServerClient ← Response ← ServerUse cases:
Multiple requests, multiple responses. Both sides stream.
service ChatService { rpc Chat(stream ChatMessage) returns (stream ChatMessage);}Flow:
Client → Message 1 → ServerClient ← Message 2 ← ServerClient → Message 3 → ServerClient ← Message 4 ← Server...Use cases:
import grpcfrom concurrent import futuresimport user_pb2import user_pb2_grpc
class UserService(user_pb2_grpc.UserServiceServicer): def GetUser(self, request, context): """Unary RPC implementation""" user_id = request.user_id
# Fetch user from database user = user_repository.find_by_id(user_id)
if not user: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details("User not found") return user_pb2.GetUserResponse()
# Return response return user_pb2.GetUserResponse( user=user_pb2.User( id=user.id, name=user.name, email=user.email, age=user.age ) )
def ListUsers(self, request, context): """Server streaming implementation""" users = user_repository.find_all()
for user in users: yield user_pb2.User( id=user.id, name=user.name, email=user.email, age=user.age )
def CreateUsers(self, request_iterator, context): """Client streaming implementation""" created_count = 0
for user_request in request_iterator: # Create user user_repository.create( name=user_request.name, email=user_request.email, age=user_request.age ) created_count += 1
return user_pb2.CreateUsersResponse( created_count=created_count )
def ChatUsers(self, request_iterator, context): """Bidirectional streaming implementation""" for message in request_iterator: # Process message response = process_message(message)
# Send response yield response
def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) user_pb2_grpc.add_UserServiceServicer_to_server( UserService(), server ) server.add_insecure_port('[::]:50051') server.start() server.wait_for_termination()
if __name__ == '__main__': serve()import io.grpc.Server;import io.grpc.ServerBuilder;import io.grpc.stub.StreamObserver;import user.UserServiceGrpc;import user.UserOuterClass;
public class UserService extends UserServiceGrpc.UserServiceImplBase {
@Override public void getUser( UserOuterClass.GetUserRequest request, StreamObserver<UserOuterClass.GetUserResponse> responseObserver) { // Unary RPC implementation int userId = request.getUserId();
// Fetch user from database User user = userRepository.findById(userId) .orElseThrow(() -> Status.NOT_FOUND .withDescription("User not found") .asRuntimeException());
// Build response UserOuterClass.GetUserResponse response = UserOuterClass.GetUserResponse.newBuilder() .setUser(UserOuterClass.User.newBuilder() .setId(user.getId()) .setName(user.getName()) .setEmail(user.getEmail()) .setAge(user.getAge()) .build()) .build();
responseObserver.onNext(response); responseObserver.onCompleted(); }
@Override public void listUsers( UserOuterClass.GetUserRequest request, StreamObserver<UserOuterClass.User> responseObserver) { // Server streaming implementation List<User> users = userRepository.findAll();
for (User user : users) { UserOuterClass.User protoUser = UserOuterClass.User.newBuilder() .setId(user.getId()) .setName(user.getName()) .setEmail(user.getEmail()) .setAge(user.getAge()) .build();
responseObserver.onNext(protoUser); }
responseObserver.onCompleted(); }
@Override public StreamObserver<UserOuterClass.User> createUsers( StreamObserver<UserOuterClass.CreateUsersResponse> responseObserver) { // Client streaming implementation return new StreamObserver<UserOuterClass.User>() { private int createdCount = 0;
@Override public void onNext(UserOuterClass.User user) { // Create user userRepository.create( user.getName(), user.getEmail(), user.getAge() ); createdCount++; }
@Override public void onError(Throwable t) { responseObserver.onError(t); }
@Override public void onCompleted() { UserOuterClass.CreateUsersResponse response = UserOuterClass.CreateUsersResponse.newBuilder() .setCreatedCount(createdCount) .build();
responseObserver.onNext(response); responseObserver.onCompleted(); } }; }
public static void main(String[] args) throws Exception { Server server = ServerBuilder.forPort(50051) .addService(new UserService()) .build() .start();
server.awaitTermination(); }}import * as grpc from '@grpc/grpc-js';import * as protoLoader from '@grpc/proto-loader';import { UserService, UserServiceService } from './generated/user_grpc_pb';import { GetUserRequest, GetUserResponse, User } from './generated/user_pb';
class UserServiceImpl implements UserServiceService { getUser( call: grpc.ServerUnaryCall<GetUserRequest, GetUserResponse>, callback: grpc.sendUnaryData<GetUserResponse> ): void { // Unary RPC implementation const userId = call.request.getUserId();
// Fetch user from database const user = userRepository.findById(userId);
if (!user) { callback({ code: grpc.status.NOT_FOUND, message: 'User not found' }); return; }
// Build response const response = new GetUserResponse(); const protoUser = new User(); protoUser.setId(user.id); protoUser.setName(user.name); protoUser.setEmail(user.email); protoUser.setAge(user.age); response.setUser(protoUser);
callback(null, response); }
listUsers(call: grpc.ServerWritableStream<GetUserRequest, User>): void { // Server streaming implementation const users = userRepository.findAll();
users.forEach(user => { const protoUser = new User(); protoUser.setId(user.id); protoUser.setName(user.name); protoUser.setEmail(user.email); protoUser.setAge(user.age); call.write(protoUser); });
call.end(); }
createUsers( call: grpc.ServerReadableStream<User, CreateUsersResponse>, callback: grpc.sendUnaryData<CreateUsersResponse> ): void { // Client streaming implementation let createdCount = 0;
call.on('data', (user: User) => { // Create user userRepository.create({ name: user.getName(), email: user.getEmail(), age: user.getAge() }); createdCount++; });
call.on('end', () => { const response = new CreateUsersResponse(); response.setCreatedCount(createdCount); callback(null, response); }); }}
function serve() { const server = new grpc.Server(); server.addService(UserServiceService, new UserServiceImpl()); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { server.start(); });}
serve();#include <grpcpp/grpcpp.h>#include "user.grpc.pb.h"
class UserServiceImpl final : public user::UserService::Service { // Server implementation grpc::Status GetUser( grpc::ServerContext* context, const user::GetUserRequest* request, user::GetUserResponse* response ) override { // Unary RPC implementation int userId = request->user_id();
// Fetch user from database auto user = userRepository.findById(userId);
if (!user.has_value()) { return grpc::Status(grpc::StatusCode::NOT_FOUND, "User not found"); }
// Build response user::User* protoUser = response->mutable_user(); protoUser->set_id(user->getId()); protoUser->set_name(user->getName()); protoUser->set_email(user->getEmail()); protoUser->set_age(user->getAge());
return grpc::Status::OK; }
grpc::Status ListUsers( grpc::ServerContext* context, const user::GetUserRequest* request, grpc::ServerWriter<user::User>* writer ) override { // Server streaming implementation auto users = userRepository.findAll();
for (const auto& user : users) { user::User protoUser; protoUser.set_id(user.getId()); protoUser.set_name(user.getName()); protoUser.set_email(user.getEmail()); protoUser.set_age(user.getAge());
if (!writer->Write(protoUser)) { break; } }
return grpc::Status::OK; }};
void RunServer() { std::string server_address("0.0.0.0:50051"); UserServiceImpl service;
grpc::ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); server->Wait();}using Grpc.Core;using UserService;
public class UserServiceImpl : UserService.UserServiceBase { // Server implementation public override Task<GetUserResponse> GetUser( GetUserRequest request, ServerCallContext context ) { // Unary RPC implementation int userId = request.UserId;
// Fetch user from database var user = userRepository.FindById(userId);
if (user == null) { throw new RpcException( new Status(StatusCode.NotFound, "User not found") ); }
// Build response return Task.FromResult(new GetUserResponse { User = new User { Id = user.Id, Name = user.Name, Email = user.Email, Age = user.Age } }); }
public override async Task ListUsers( GetUserRequest request, IServerStreamWriter<User> responseStream, ServerCallContext context ) { // Server streaming implementation var users = userRepository.FindAll();
foreach (var user in users) { await responseStream.WriteAsync(new User { Id = user.Id, Name = user.Name, Email = user.Email, Age = user.Age }); } }
public override async Task<CreateUsersResponse> CreateUsers( IAsyncStreamReader<User> requestStream, ServerCallContext context ) { // Client streaming implementation int createdCount = 0;
while (await requestStream.MoveNext()) { var user = requestStream.Current; userRepository.Create(new UserEntity { Name = user.Name, Email = user.Email, Age = user.Age }); createdCount++; }
return new CreateUsersResponse { CreatedCount = createdCount }; }}import grpcimport user_pb2import user_pb2_grpc
def get_user(user_id): """Unary RPC client""" with grpc.insecure_channel('localhost:50051') as channel: stub = user_pb2_grpc.UserServiceStub(channel)
request = user_pb2.GetUserRequest(user_id=user_id) response = stub.GetUser(request)
return response.user
def list_users(): """Server streaming client""" with grpc.insecure_channel('localhost:50051') as channel: stub = user_pb2_grpc.UserServiceStub(channel)
request = user_pb2.GetUserRequest()
for user in stub.ListUsers(request): print(f"User: {user.name}")
def create_users(users_data): """Client streaming client""" with grpc.insecure_channel('localhost:50051') as channel: stub = user_pb2_grpc.UserServiceStub(channel)
def user_generator(): for user_data in users_data: yield user_pb2.User( name=user_data['name'], email=user_data['email'], age=user_data['age'] )
response = stub.CreateUsers(user_generator()) print(f"Created {response.created_count} users")import io.grpc.ManagedChannel;import io.grpc.ManagedChannelBuilder;import user.UserServiceGrpc;import user.UserOuterClass;
public class GrpcClient { private final ManagedChannel channel; private final UserServiceGrpc.UserServiceBlockingStub blockingStub;
public GrpcClient(String host, int port) { this.channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build(); this.blockingStub = UserServiceGrpc.newBlockingStub(channel); }
public UserOuterClass.User getUser(int userId) { // Unary RPC client UserOuterClass.GetUserRequest request = UserOuterClass.GetUserRequest.newBuilder() .setUserId(userId) .build();
UserOuterClass.GetUserResponse response = blockingStub.getUser(request); return response.getUser(); }
public void listUsers() { // Server streaming client UserOuterClass.GetUserRequest request = UserOuterClass.GetUserRequest.newBuilder().build();
blockingStub.listUsers(request).forEachRemaining(user -> { System.out.println("User: " + user.getName()); }); }
public void createUsers(List<UserData> usersData) { // Client streaming client UserServiceGrpc.UserServiceStub asyncStub = UserServiceGrpc.newStub(channel);
StreamObserver<UserOuterClass.User> requestObserver = asyncStub.createUsers( new StreamObserver<UserOuterClass.CreateUsersResponse>() { @Override public void onNext(UserOuterClass.CreateUsersResponse response) { System.out.println("Created " + response.getCreatedCount() + " users"); }
@Override public void onError(Throwable t) { t.printStackTrace(); }
@Override public void onCompleted() { // Done } } );
for (UserData userData : usersData) { UserOuterClass.User user = UserOuterClass.User.newBuilder() .setName(userData.getName()) .setEmail(userData.getEmail()) .setAge(userData.getAge()) .build();
requestObserver.onNext(user); }
requestObserver.onCompleted(); }}import * as grpc from '@grpc/grpc-js';import { UserServiceClient } from './generated/user_grpc_pb';import { GetUserRequest, User, CreateUsersResponse } from './generated/user_pb';
function getUser(userId: number): Promise<User> { // Unary RPC client const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure());
const request = new GetUserRequest(); request.setUserId(userId);
return new Promise((resolve, reject) => { client.getUser(request, (error, response) => { if (error) { reject(error); } else { resolve(response.getUser()); } }); });}
function listUsers(): void { // Server streaming client const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure()); const request = new GetUserRequest();
const call = client.listUsers(request); call.on('data', (user: User) => { console.log(`User: ${user.getName()}`); }); call.on('end', () => { console.log('Stream ended'); });}
function createUsers(usersData: Array<{name: string, email: string, age: number}>): Promise<number> { // Client streaming client const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure()); const call = client.createUsers((error, response) => { if (error) { console.error(error); } else { console.log(`Created ${response.getCreatedCount()} users`); } });
usersData.forEach(userData => { const user = new User(); user.setName(userData.name); user.setEmail(userData.email); user.setAge(userData.age); call.write(user); });
call.end();
return Promise.resolve(usersData.length);}#include <grpcpp/grpcpp.h>#include "user.grpc.pb.h"
class GrpcClient {private: std::unique_ptr<user::UserService::Stub> stub_;
public: GrpcClient(std::shared_ptr<grpc::Channel> channel) : stub_(user::UserService::NewStub(channel)) {}
user::User getUser(int userId) { // Unary RPC client user::GetUserRequest request; request.set_user_id(userId);
user::GetUserResponse response; grpc::ClientContext context;
grpc::Status status = stub_->GetUser(&context, request, &response);
if (status.ok()) { return response.user(); } else { throw std::runtime_error(status.error_message()); } }
void listUsers() { // Server streaming client user::GetUserRequest request; grpc::ClientContext context;
std::unique_ptr<grpc::ClientReader<user::User>> reader( stub_->ListUsers(&context, request) );
user::User user; while (reader->Read(&user)) { std::cout << "User: " << user.name() << std::endl; }
grpc::Status status = reader->Finish(); if (!status.ok()) { throw std::runtime_error(status.error_message()); } }};using Grpc.Net.Client;using UserService;
public class GrpcClient { private readonly UserService.UserServiceClient client;
public GrpcClient(string address) { var channel = GrpcChannel.ForAddress(address); this.client = new UserService.UserServiceClient(channel); }
public async Task<User> GetUserAsync(int userId) { // Unary RPC client var request = new GetUserRequest { UserId = userId }; var response = await client.GetUserAsync(request); return response.User; }
public async Task ListUsersAsync() { // Server streaming client var request = new GetUserRequest(); using var call = client.ListUsers(request);
await foreach (var user in call.ResponseStream.ReadAllAsync()) { Console.WriteLine($"User: {user.Name}"); } }
public async Task<int> CreateUsersAsync(IEnumerable<UserData> usersData) { // Client streaming client using var call = client.CreateUsers();
foreach (var userData in usersData) { await call.RequestStream.WriteAsync(new User { Name = userData.Name, Email = userData.Email, Age = userData.Age }); }
await call.RequestStream.CompleteAsync(); var response = await call; return response.CreatedCount; }}gRPC uses status codes (similar to HTTP but different):
| Code | Meaning | HTTP Equivalent |
|---|---|---|
OK | Success | 200 |
INVALID_ARGUMENT | Bad request | 400 |
NOT_FOUND | Resource not found | 404 |
ALREADY_EXISTS | Conflict | 409 |
PERMISSION_DENIED | Forbidden | 403 |
UNAUTHENTICATED | Unauthorized | 401 |
RESOURCE_EXHAUSTED | Rate limited | 429 |
INTERNAL | Server error | 500 |
import grpcfrom grpc import StatusCode
def GetUser(self, request, context): try: user = user_repository.find_by_id(request.user_id)
if not user: context.set_code(StatusCode.NOT_FOUND) context.set_details("User not found") return user_pb2.GetUserResponse()
return user_pb2.GetUserResponse(user=user)
except ValueError as e: context.set_code(StatusCode.INVALID_ARGUMENT) context.set_details(str(e)) return user_pb2.GetUserResponse()
except Exception as e: context.set_code(StatusCode.INTERNAL) context.set_details("Internal server error") return user_pb2.GetUserResponse()@Overridepublic void getUser( UserOuterClass.GetUserRequest request, StreamObserver<UserOuterClass.GetUserResponse> responseObserver) { try { User user = userRepository.findById(request.getUserId()) .orElseThrow(() -> Status.NOT_FOUND .withDescription("User not found") .asRuntimeException());
UserOuterClass.GetUserResponse response = buildResponse(user); responseObserver.onNext(response); responseObserver.onCompleted();
} catch (IllegalArgumentException e) { responseObserver.onError(Status.INVALID_ARGUMENT .withDescription(e.getMessage()) .asRuntimeException());
} catch (Exception e) { responseObserver.onError(Status.INTERNAL .withDescription("Internal server error") .asRuntimeException()); }}import * as grpc from '@grpc/grpc-js';
getUser( call: grpc.ServerUnaryCall<GetUserRequest, GetUserResponse>, callback: grpc.sendUnaryData<GetUserResponse>): void { try { const user = userRepository.findById(call.request.getUserId());
if (!user) { callback({ code: grpc.status.NOT_FOUND, message: 'User not found' }); return; }
const response = buildResponse(user); callback(null, response);
} catch (error) { if (error instanceof ValueError) { callback({ code: grpc.status.INVALID_ARGUMENT, message: error.message }); } else { callback({ code: grpc.status.INTERNAL, message: 'Internal server error' }); } }}grpc::Status GetUser( grpc::ServerContext* context, const user::GetUserRequest* request, user::GetUserResponse* response) override { try { auto user = userRepository.findById(request->user_id());
if (!user.has_value()) { return grpc::Status(grpc::StatusCode::NOT_FOUND, "User not found"); }
// Build response buildResponse(user.value(), response); return grpc::Status::OK;
} catch (const std::invalid_argument& e) { return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, e.what());
} catch (const std::exception& e) { return grpc::Status(grpc::StatusCode::INTERNAL, "Internal server error"); }}public override Task<GetUserResponse> GetUser( GetUserRequest request, ServerCallContext context) { try { var user = userRepository.FindById(request.UserId);
if (user == null) { throw new RpcException( new Status(StatusCode.NotFound, "User not found") ); }
return Task.FromResult(buildResponse(user));
} catch (ArgumentException e) { throw new RpcException( new Status(StatusCode.InvalidArgument, e.Message) );
} catch (Exception e) { throw new RpcException( new Status(StatusCode.Internal, "Internal server error") ); }}Typical performance differences:
| Metric | REST/JSON | gRPC |
|---|---|---|
| Latency | 10-50ms | 2-10ms |
| Throughput | 1K-10K req/s | 10K-100K req/s |
| Payload Size | 100% | 30-50% |
| CPU Usage | Higher | Lower |
Why gRPC is faster:
At the code level, gRPC translates to service interfaces, message types, and streaming handlers.
# Design service interfaceclass UserServiceInterface: """Service interface defining contract"""
def get_user(self, user_id: int) -> User: """Unary: Get single user""" pass
def list_users(self, filters: dict) -> Iterator[User]: """Server streaming: List users""" pass
def create_users(self, users: Iterator[User]) -> int: """Client streaming: Create multiple users""" pass
# Implementationclass UserService(UserServiceInterface): def __init__(self, repository: UserRepository): self.repository = repository
def get_user(self, user_id: int) -> User: return self.repository.find_by_id(user_id)
def list_users(self, filters: dict) -> Iterator[User]: return self.repository.find_all(filters)
def create_users(self, users: Iterator[User]) -> int: count = 0 for user in users: self.repository.create(user) count += 1 return count// Service interfaceinterface UserService { User getUser(int userId); Stream<User> listUsers(Map<String, String> filters); int createUsers(Stream<User> users);}
// Implementationclass UserServiceImpl implements UserService { private final UserRepository repository;
public UserServiceImpl(UserRepository repository) { this.repository = repository; }
@Override public User getUser(int userId) { return repository.findById(userId).orElseThrow(); }
@Override public Stream<User> listUsers(Map<String, String> filters) { return repository.findAll(filters).stream(); }
@Override public int createUsers(Stream<User> users) { AtomicInteger count = new AtomicInteger(); users.forEach(user -> { repository.create(user); count.incrementAndGet(); }); return count.get(); }}// Service interfaceinterface UserService { getUser(userId: number): Promise<User>; listUsers(filters: Record<string, string>): AsyncIterable<User>; createUsers(users: AsyncIterable<User>): Promise<number>;}
// Implementationclass UserServiceImpl implements UserService { constructor(private repository: UserRepository) {}
async getUser(userId: number): Promise<User> { // Unary: Get single user return await this.repository.findById(userId); }
async* listUsers(filters: Record<string, string>): AsyncIterable<User> { // Server streaming: List users const users = await this.repository.findAll(filters); for (const user of users) { yield user; } }
async createUsers(users: AsyncIterable<User>): Promise<number> { // Client streaming: Create multiple users let count = 0; for await (const user of users) { await this.repository.create(user); count++; } return count; }}// Service interfaceclass UserService {public: virtual std::optional<User> getUser(int userId) = 0; virtual std::vector<User> listUsers(const std::map<std::string, std::string>& filters) = 0; virtual int createUsers(const std::vector<User>& users) = 0;};
// Implementationclass UserServiceImpl : public UserService {private: UserRepository& repository;
public: UserServiceImpl(UserRepository& repo) : repository(repo) {}
std::optional<User> getUser(int userId) override { // Unary: Get single user return repository.findById(userId); }
std::vector<User> listUsers(const std::map<std::string, std::string>& filters) override { // Server streaming: List users return repository.findAll(filters); }
int createUsers(const std::vector<User>& users) override { // Client streaming: Create multiple users int count = 0; for (const auto& user : users) { repository.create(user); count++; } return count; }};// Service interfacepublic interface IUserService { Task<User> GetUserAsync(int userId); IAsyncEnumerable<User> ListUsersAsync(Dictionary<string, string> filters); Task<int> CreateUsersAsync(IAsyncEnumerable<User> users);}
// Implementationpublic class UserService : IUserService { private readonly UserRepository repository;
public UserService(UserRepository repository) { this.repository = repository; }
public async Task<User> GetUserAsync(int userId) { // Unary: Get single user return await repository.FindByIdAsync(userId); }
public async IAsyncEnumerable<User> ListUsersAsync(Dictionary<string, string> filters) { // Server streaming: List users var users = await repository.FindAllAsync(filters); foreach (var user in users) { yield return user; } }
public async Task<int> CreateUsersAsync(IAsyncEnumerable<User> users) { // Client streaming: Create multiple users int count = 0; await foreach (var user in users) { await repository.CreateAsync(user); count++; } return count; }}Performance First
gRPC is 5-10x faster than REST due to binary encoding and HTTP/2. Perfect for microservices.
Streaming Support
gRPC supports four streaming types: unary, server, client, and bidirectional. REST can’t do this.
Strong Typing
Protocol Buffers provide compile-time type safety. Schema defines contract, code is generated.
Service Contracts
.proto files define service contracts. Generate client/server code in any language automatically.