gRPC y Protocol Buffers
Aprende gRPC para comunicación RPC de alto rendimiento y tipada con Protocol Buffers
¿Qué es gRPC?
gRPC (gRPC Remote Procedure Call) es un framework RPC de código abierto y alto rendimiento desarrollado por Google. Utiliza Protocol Buffers (protobuf) para serialización y HTTP/2 para transporte, haciéndolo significativamente más rápido que las APIs REST para muchos casos de uso.
gRPC es ideal para comunicación de microservicios, streaming en tiempo real y escenarios donde el rendimiento y la seguridad de tipos son críticos.
⚡ gRPC vs REST
REST
- • JSON/XML - Legible para humanos
- • HTTP/1.1 - Basado en texto
- • Solo Solicitud/Respuesta
- • Tipado débil
- • Soporte nativo del navegador
gRPC
- • Protobuf - Binario, compacto
- • HTTP/2 - Streams multiplexados
- • Soporte de streaming
- • Contratos fuertemente tipados
- • Serialización 10x más rápida
Protocol Buffers (Protobuf)
Protocol Buffers definen la estructura de tus datos y servicios:
// user.proto - Definición de Protocol Buffer
syntax = "proto3";
package user;
// Tipos de mensaje (como interfaces de TypeScript)
message User {
int32 id = 1;
string name = 2;
string email = 3;
UserRole role = 4;
repeated string tags = 5; // Array
optional string bio = 6; // Campo opcional
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_ADMIN = 1;
USER_ROLE_MEMBER = 2;
}
message GetUserRequest {
int32 id = 1;
}
message GetUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message UserList {
repeated User users = 1;
int32 total = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
UserRole role = 3;
}
// Definición de servicio (como endpoints REST)
service UserService {
// RPC unario (solicitud-respuesta)
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
// Streaming del servidor
rpc ListUsers(GetUsersRequest) returns (stream User);
// Streaming del cliente
rpc UploadUsers(stream User) returns (UserList);
// Streaming bidireccional
rpc Chat(stream Message) returns (stream Message);
}
Patrones de Comunicación gRPC
1. RPC Unario
Una solicitud, una respuesta. Como REST.
Cliente → Solicitud → Servidor
Cliente ← Respuesta ← Servidor
2. Streaming del Servidor
Una solicitud, stream de respuestas.
Cliente → Solicitud → Servidor
Cliente ← Respuesta 1 ← Servidor
Cliente ← Respuesta 2 ← Servidor
Cliente ← Respuesta N ← Servidor
3. Streaming del Cliente
Stream de solicitudes, una respuesta.
Cliente → Solicitud 1 → Servidor
Cliente → Solicitud 2 → Servidor
Cliente → Solicitud N → Servidor
Cliente ← Respuesta ← Servidor
4. Streaming Bidireccional
Ambos lados transmiten independientemente.
Cliente ↔ Servidor
(Ambos envían/reciben en cualquier momento)
gRPC-Web para Navegadores
Los navegadores no pueden usar gRPC directamente. Usa gRPC-Web con un proxy:
// Instalar gRPC-Web
// npm install grpc-web
// Cliente generado desde archivo .proto
import { UserServiceClient } from './generated/user_grpc_web_pb';
import { GetUserRequest, User } from './generated/user_pb';
// Crear cliente (conecta al proxy Envoy)
const client = new UserServiceClient('https://api.example.com');
// Llamada unaria
async function getUser(id: number): Promise {
const request = new GetUserRequest();
request.setId(id);
return new Promise((resolve, reject) => {
client.getUser(request, {}, (err, response) => {
if (err) {
reject(err);
} else {
resolve(response);
}
});
});
}
// Streaming del servidor
function listUsers() {
const request = new GetUsersRequest();
request.setPage(1);
request.setLimit(10);
const stream = client.listUsers(request, {});
stream.on('data', (user: User) => {
console.log('Usuario recibido:', user.toObject());
});
stream.on('error', (err) => {
console.error('Error de stream:', err);
});
stream.on('end', () => {
console.log('Stream finalizado');
});
}
// Uso
const user = await getUser(123);
console.log(user.getName(), user.getEmail());
Servidor gRPC en Node.js
// server.js - servidor gRPC en Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Cargar archivo proto
const packageDefinition = protoLoader.loadSync('user.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// Base de datos en memoria
const users = new Map();
let nextId = 1;
// Implementación del servicio
const userService = {
// RPC unario
getUser(call, callback) {
const user = users.get(call.request.id);
if (user) {
callback(null, user);
} else {
callback({
code: grpc.status.NOT_FOUND,
message: 'Usuario no encontrado'
});
}
},
// RPC unario
createUser(call, callback) {
const user = {
id: nextId++,
name: call.request.name,
email: call.request.email,
role: call.request.role || 'USER_ROLE_MEMBER'
};
users.set(user.id, user);
callback(null, user);
},
// Streaming del servidor
listUsers(call) {
const { page, limit } = call.request;
const allUsers = Array.from(users.values());
const start = (page - 1) * limit;
const pageUsers = allUsers.slice(start, start + limit);
// Transmitir cada usuario
for (const user of pageUsers) {
call.write(user);
}
call.end();
},
// Streaming del cliente
uploadUsers(call, callback) {
const uploadedUsers = [];
call.on('data', (user) => {
user.id = nextId++;
users.set(user.id, user);
uploadedUsers.push(user);
});
call.on('end', () => {
callback(null, {
users: uploadedUsers,
total: uploadedUsers.length
});
});
}
};
// Iniciar servidor
const server = new grpc.Server();
server.addService(userProto.UserService.service, userService);
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) throw err;
console.log(`Servidor gRPC ejecutándose en puerto ${port}`);
server.start();
}
);
Cliente gRPC en Node.js
// client.js - cliente gRPC en Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// Crear cliente
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// Promisificar para async/await
function promisify(method) {
return (request) => new Promise((resolve, reject) => {
method.call(client, request, (err, response) => {
if (err) reject(err);
else resolve(response);
});
});
}
const getUser = promisify(client.getUser);
const createUser = promisify(client.createUser);
// Uso
async function main() {
// Crear usuario
const newUser = await createUser({
name: 'Juan Pérez',
email: 'juan@example.com',
role: 'USER_ROLE_ADMIN'
});
console.log('Creado:', newUser);
// Obtener usuario
const user = await getUser({ id: newUser.id });
console.log('Recuperado:', user);
// Streaming del servidor
const stream = client.listUsers({ page: 1, limit: 10 });
stream.on('data', (user) => {
console.log('Usuario transmitido:', user);
});
stream.on('end', () => {
console.log('Stream completado');
});
}
main().catch(console.error);
gRPC con Connect (Alternativa Moderna)
// Connect es un framework moderno compatible con gRPC
// ¡Funciona en navegadores sin proxy!
// npm install @connectrpc/connect @connectrpc/connect-web
import { createPromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { UserService } from './generated/user_connect';
// Crear transporte
const transport = createConnectTransport({
baseUrl: 'https://api.example.com',
});
// Crear cliente tipado
const client = createPromiseClient(UserService, transport);
// ¡Totalmente tipado, amigable con async/await!
async function main() {
// Llamada unaria
const user = await client.getUser({ id: 123 });
console.log(user.name, user.email);
// Crear usuario
const newUser = await client.createUser({
name: 'Jane Doe',
email: 'jane@example.com',
role: 'USER_ROLE_MEMBER'
});
// Streaming del servidor
for await (const user of client.listUsers({ page: 1, limit: 10 })) {
console.log('Recibido:', user.name);
}
}
main();
Cuándo Usar gRPC
✅ Usar gRPC Cuando
- • Comunicación de microservicios
- • Se necesita alto rendimiento
- • Streaming en tiempo real
- • Entornos políglotas
- • Se requiere tipado fuerte
- • Aplicaciones móviles (ancho de banda importa)
⚠️ Considerar REST Cuando
- • APIs públicas (mayor compatibilidad)
- • Aplicaciones enfocadas en navegadores
- • Operaciones CRUD simples
- • Equipo no familiarizado con gRPC
- • Se necesita depuración legible por humanos
💡 Mejores Prácticas de gRPC
- ✓ Versiona tus protos - Usa versionado de paquetes (v1, v2)
- ✓ Usa números de campo con cuidado - Nunca los reutilices o cambies
- ✓ Implementa plazos - Siempre establece tiempos de espera en llamadas
- ✓ Maneja errores de streaming - Los streams pueden fallar a mitad de transferencia
- ✓ Usa gRPC-Web o Connect - Para compatibilidad con navegadores
- ✓ Monitorea con interceptores - Agrega logging y métricas