Implementar servicios de pedidos, inventario y pagos con gRPC

Lectura
30 min~6 min lectura

Concepto clave

En un sistema de microservicios, la comunicación entre servicios es fundamental. gRPC (Google Remote Procedure Call) es un framework de alto rendimiento que utiliza Protocol Buffers (protobuf) como lenguaje de definición de interfaz y formato de serialización. A diferencia de REST/JSON, gRPC es binario, tipado y utiliza HTTP/2, lo que permite comunicación bidireccional, streaming y menor latencia.

Imagina que los microservicios son departamentos en una empresa: Ventas (pedidos), Almacén (inventario) y Contabilidad (pagos). En lugar de enviar emails (REST/JSON) con documentos largos y sin estructura, usan formularios estandarizados (protobuf) y reuniones rápidas por videollamada (gRPC) donde cada campo tiene un tipo específico y las respuestas son inmediatas. Esto elimina malentendidos y acelera los procesos.

Para nuestro proyecto integrador, implementaremos tres servicios: Pedidos (crea y gestiona órdenes), Inventario (verifica y actualiza stock) y Pagos (procesa transacciones). Cada servicio tendrá su propia definición en protobuf, generará código en el lenguaje de programación (ej. Go, Python) y se comunicará mediante llamadas gRPC sincrónicas o asíncronas según la necesidad.

Cómo funciona en la práctica

Vamos a desglosar la implementación paso a paso para un flujo típico: crear un pedido.

  1. Definir protobuf: Creamos archivos .proto para cada servicio. Ejemplo para servicio de pedidos:
    syntax = "proto3";
    package ecommerce;
    
    service OrderService {
      rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
      rpc GetOrder(GetOrderRequest) returns (OrderResponse);
    }
    
    message CreateOrderRequest {
      string user_id = 1;
      repeated OrderItem items = 2;
      float total_amount = 3;
    }
    
    message OrderItem {
      string product_id = 1;
      int32 quantity = 2;
      float price = 3;
    }
    
    message OrderResponse {
      string order_id = 1;
      string status = 2; // e.g., "pending", "completed", "failed"
      string message = 3;
    }
  2. Generar código: Usamos el compilador protoc para generar código cliente y servidor. Ejemplo en Go: protoc --go_out=. --go-grpc_out=. order.proto. Esto crea structs y interfaces listas para implementar.
  3. Implementar servidor gRPC: En el servicio de pedidos, implementamos la lógica de negocio. Cuando se recibe CreateOrder, validamos los datos, llamamos al servicio de inventario (vía gRPC) para verificar stock, y si hay disponibilidad, llamamos al servicio de pagos para procesar el pago.
  4. Configurar comunicación: Usamos balanceo de carga, timeouts y retries. En Kubernetes, exponemos los servicios mediante ClusterIP y usamos service discovery.

Una tabla de comparación entre llamadas gRPC y REST para este caso:

AspectogRPCREST/JSON
FormatoBinario (protobuf)Texto (JSON)
TipadoFuerte, definido en .protoDébil, depende de validación
PerformanceAlta (menos overhead)Moderada
StreamingSoporte nativo (HTTP/2)Limitado

Caso de estudio

Consideremos una plataforma de e-commerce como Amazon. Cuando un usuario hace un pedido, el sistema debe:

  1. Servicio de Pedidos: Recibe la solicitud, valida los items y crea una orden en estado "pendiente".
  2. Servicio de Inventario: Verifica el stock para cada producto. Si algún item no tiene stock suficiente, responde con error y el pedido se cancela.
  3. Servicio de Pagos: Procesa el pago con la pasarela (ej. Stripe). Si es exitoso, actualiza el estado del pedido a "pagado" y notifica al inventario para reducir stock.

Implementación con gRPC: Cada servicio tiene su servidor gRPC. El servicio de pedidos actúa como orquestador. Ejemplo de flujo en código Go:

// En OrderService (servidor)
func (s *OrderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.OrderResponse, error) {
    // 1. Llamar a InventoryService para verificar stock
    inventoryClient := pb.NewInventoryServiceClient(inventoryConn)
    inventoryResp, err := inventoryClient.CheckStock(ctx, &pb.StockRequest{Items: req.Items})
    if err != nil || !inventoryResp.Available {
        return &pb.OrderResponse{Status: "failed", Message: "Stock insuficiente"}, nil
    }
    
    // 2. Llamar a PaymentService para procesar pago
    paymentClient := pb.NewPaymentServiceClient(paymentConn)
    paymentResp, err := paymentClient.ProcessPayment(ctx, &pb.PaymentRequest{Amount: req.TotalAmount, UserId: req.UserId})
    if err != nil || paymentResp.Status != "success" {
        return &pb.OrderResponse{Status: "failed", Message: "Pago fallido"}, nil
    }
    
    // 3. Actualizar orden y responder
    orderId := generateOrderID()
    return &pb.OrderResponse{OrderId: orderId, Status: "completed", Message: "Pedido creado exitosamente"}, nil
}
En producción, se deben manejar transacciones distribuidas (Sagas) para mantener consistencia eventual entre servicios.

Errores comunes

  • No definir versiones en protobuf: Cambiar campos sin considerar compatibilidad hacia atrás puede romper clientes. Solución: Usar campos opcionales, no eliminar campos numerados, y planificar versiones con cuidado.
  • Ignorar timeouts y retries: En microservicios, las llamadas pueden fallar por red o latencia. Solución: Configurar timeouts apropiados (ej. 5-10 segundos) e implementar retries con backoff exponencial.
  • Acoplamiento fuerte entre servicios: Si el servicio de pedidos depende directamente de la implementación de inventario, cambios pueden causar errores. Solución: Usar contratos definidos en protobuf y mantener independencia; considerar eventos asíncronos para desacoplar.
  • No manejar errores de gRPC: gRPC devuelve códigos de estado específicos (ej. DEADLINE_EXCEEDED, UNAVAILABLE). Solución: Implementar logging y métricas para monitorear, y definir políticas de fallback.
  • Olvidar la seguridad: Exponer endpoints gRPC sin autenticación o encriptación. Solución: Usar TLS para encriptar tráfico y tokens (ej. JWT) para autenticación.

Checklist de dominio

  1. ¿Puedes definir un servicio gRPC con protobuf que incluya al menos dos métodos RPC y mensajes anidados?
  2. ¿Sabes generar código a partir de archivos .proto para tu lenguaje de programación (ej. Go, Python, Java)?
  3. ¿Puedes implementar un servidor gRPC que orquestre llamadas a otros dos servicios (ej. inventario y pagos)?
  4. ¿Entiendes cómo configurar timeouts, retries y balanceo de carga en un cliente gRPC?
  5. ¿Eres capaz de desplegar los servicios en Kubernetes y configurar service discovery para comunicación?
  6. ¿Puedes manejar errores comunes como fallos de red o respuestas inesperadas entre servicios?
  7. ¿Sabes medir el performance de las llamadas gRPC (ej. latencia, throughput) y optimizar según necesidades?

Implementar un flujo completo de pedido con gRPC en Kubernetes

En este ejercicio, crearás un sistema de tres microservicios (pedidos, inventario, pagos) que se comuniquen via gRPC y los desplegarás en un cluster de Kubernetes. Sigue estos pasos:

  1. Preparar el entorno: Asegúrate de tener Docker, Kubernetes (ej. Minikube o Kind) y el compilador protoc instalados.
  2. Definir protobuf: Crea tres archivos .proto: order.proto, inventory.proto y payment.proto. Define al menos un método RPC en cada uno (ej. CreateOrder, CheckStock, ProcessPayment) con mensajes apropiados.
  3. Generar código: Usa protoc para generar código en tu lenguaje preferido (ej. Go). Ejecuta comandos como protoc --go_out=. --go-grpc_out=. *.proto.
  4. Implementar servidores: Escribe el código para cada servicio. El servicio de pedidos debe llamar a inventario para verificar stock y a pagos para procesar el pago, manejando errores y timeouts.
  5. Construir imágenes Docker: Crea un Dockerfile para cada servicio y construye las imágenes. Etiquétalas apropiadamente (ej. order-service:latest).
  6. Desplegar en Kubernetes: Crea archivos YAML para Deployments y Services. Usa ClusterIP para comunicación interna. Asegúrate de que los servicios puedan descubrirse por nombre (ej. inventory-service).
  7. Probar el flujo: Expón el servicio de pedidos mediante NodePort o Ingress. Usa una herramienta como grpcurl o un cliente escrito por ti para enviar una solicitud CreateOrder y verificar que el flujo funcione end-to-end.
  8. Monitorear y optimizar: Agrega logging básico y métricas (ej. contadores de llamadas exitosas/fallidas). Ajusta timeouts y recursos según sea necesario.
Pistas
  • Usa variables de entorno en Kubernetes para configurar endpoints de servicios (ej. INVENTORY_SERVICE_HOST).
  • Implementa retries con backoff exponencial en el cliente gRPC para manejar fallos temporales.
  • Considera usar un patrón Saga para manejar transacciones distribuidas si necesitas consistencia fuerte.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.