TechLead
Avanzado
30 min
Lección 9 de 10
API

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