Implementar tu primer cliente-servidor con llamadas unarias

Video
25 min~5 min lectura

Reproductor de video

Concepto clave

Las llamadas unarias en gRPC son la forma más básica de comunicación entre cliente y servidor, donde el cliente envía una solicitud y recibe una respuesta individual. Piensa en esto como una llamada telefónica tradicional: tú marcas un número (envías una solicitud), hablas con alguien (procesamiento en el servidor) y recibes una respuesta antes de colgar. A diferencia de las llamadas HTTP REST, gRPC utiliza Protocol Buffers para definir contratos de servicio tipados que garantizan que tanto cliente como servidor hablan exactamente el mismo "idioma" de datos.

En el contexto de microservicios, las llamadas unarias son ideales para operaciones síncronas donde necesitas una respuesta inmediata para continuar con tu lógica de negocio. Por ejemplo, cuando un servicio de autenticación necesita verificar credenciales antes de permitir acceso a otro servicio. La tipificación fuerte de Protocol Buffers elimina errores de serialización comunes en JSON y reduce significativamente el overhead de red, logrando latencias de 5-10 veces menores que REST en algunos casos.

Cómo funciona en la práctica

Implementar una llamada unaria sigue un flujo estructurado:

  1. Definir el contrato: Crear un archivo .proto con el servicio y los mensajes
  2. Generar código: Usar el compilador protoc para generar clases en tu lenguaje
  3. Implementar el servidor: Crear la lógica del servicio heredando de la clase generada
  4. Implementar el cliente: Crear un canal y llamar al método remoto
  5. Ejecutar: Iniciar servidor y probar con el cliente

Veamos un ejemplo concreto. Supongamos que necesitamos un servicio para calcular impuestos:

// tax_calculator.proto
syntax = "proto3";

message TaxRequest {
  double amount = 1;
  string country_code = 2;
}

message TaxResponse {
  double tax_amount = 1;
  double total_amount = 2;
}

service TaxCalculator {
  rpc CalculateTax(TaxRequest) returns (TaxResponse);
}

Al compilar esto con protoc, obtenemos clases base que implementamos en nuestro servidor:

// Servidor en Go
type server struct {
  pb.UnimplementedTaxCalculatorServer
}

func (s *server) CalculateTax(ctx context.Context, req *pb.TaxRequest) (*pb.TaxResponse, error) {
  taxRate := getTaxRate(req.CountryCode)
  taxAmount := req.Amount * taxRate
  
  return &pb.TaxResponse{
    TaxAmount: taxAmount,
    TotalAmount: req.Amount + taxAmount,
  }, nil
}

Caso de estudio

Imagina que trabajas en una plataforma de e-commerce que procesa 10,000 transacciones por minuto. Actualmente usas REST para comunicar el servicio de órdenes con el servicio de inventario. Cada vez que se crea una orden, necesitas verificar disponibilidad de stock.

MétricaREST (JSON)gRPC (Protobuf)
Tamaño de mensaje~450 bytes~120 bytes
Tiempo de serialización2.3ms0.4ms
Latencia total15-25ms3-8ms
CPU usageAltoModerado
En este escenario real, migrar a gRPC redujo la latencia en un 70% y el ancho de banda en un 73%, permitiendo escalar sin aumentar infraestructura.

La implementación con gRPC definiría un servicio InventoryChecker con un método CheckAvailability que toma un ProductRequest y devuelve un AvailabilityResponse. La tipificación garantiza que el servicio de órdenes siempre envíe los datos correctos, eliminando errores de validación en tiempo de ejecución.

Errores comunes

  • No manejar contextos adecuadamente: Olvidar propagar deadlines y cancelaciones puede causar recursos bloqueados. Siempre verifica ctx.Err() en operaciones largas.
  • Ignorar el manejo de errores gRPC: gRPC usa códigos de estado específicos (como DEADLINE_EXCEEDED o RESOURCE_EXHAUSTED). No los conviertas automáticamente a errores genéricos.
  • Definir mensajes demasiado grandes: Protocol Buffers es eficiente, pero mensajes de más de 1MB pueden impactar performance. Considera paginación o streaming para datos grandes.
  • No versionar los contratos .proto: Cambiar mensajes existentes sin backward compatibility rompe clientes. Usa campos opcionales y nunca reutilices números de campo.
  • Configurar timeouts incorrectos: Timeouts muy cortos causan falsos errores; muy largos afectan resiliencia. Basa tus timeouts en percentiles P99 de tus mediciones reales.

Checklist de dominio

  1. Puedo definir un servicio gRPC con al menos un método unario en un archivo .proto
  2. Sé compilar el archivo .proto para generar código en mi lenguaje de programación
  3. He implementado un servidor gRPC que responde a llamadas unarias
  4. He creado un cliente que consume el servicio con manejo adecuado de errores
  5. Puedo explicar la diferencia de performance entre gRPC y REST para el mismo caso de uso
  6. Sé configurar timeouts y deadlines en el contexto de las llamadas
  7. Puedo versionar un contrato .proto manteniendo compatibilidad hacia atrás

Implementa un servicio de conversión de moneda con gRPC

En este ejercicio crearás un servicio de conversión de moneda que será consumido por otros microservicios en tu arquitectura.

  1. Define el contrato: Crea un archivo currency_converter.proto con:
    • Un mensaje ConversionRequest con campos: amount (double), from_currency (string), to_currency (string)
    • Un mensaje ConversionResponse con campos: converted_amount (double), rate_used (double)
    • Un servicio CurrencyConverter con un método unario ConvertCurrency
  2. Genera el código: Usa protoc para generar las clases en tu lenguaje preferido (Go, Java, C#, etc.)
  3. Implementa el servidor:
    • Crea una estructura que herede de la clase base generada
    • Implementa el método ConvertCurrency usando tasas de cambio fijas (ej: EUR→USD = 1.08)
    • Maneja al menos 3 pares de monedas diferentes
    • Incluye validación: amount debe ser positivo, currencies deben existir
  4. Crea el cliente:
    • Establece una conexión con el servidor
    • Implementa una función que llame a ConvertCurrency
    • Agrega un timeout de 5 segundos
    • Maneja los posibles errores gRPC (ej: moneda no soportada)
  5. Prueba la solución:
    • Inicia el servidor en el puerto 50051
    • Ejecuta el cliente con al menos 3 casos de prueba diferentes
    • Verifica que las respuestas sean correctas y los errores se manejen adecuadamente
Pistas
  • Recuerda que en Protocol Buffers 3, todos los campos son opcionales por defecto. Considera si necesitas validar valores requeridos en tu implementación.
  • Para manejar errores en gRPC, investiga cómo usar status.Errorf en Go o StatusRuntimeException en Java para devolver códigos de error específicos.
  • Al probar tu cliente, intenta enviar una moneda no soportada para verificar que tu manejo de errores funciona correctamente.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.