GRPC frente a REST

GRPC frente a REST

¿Es hora de decir adiós a las API RESTFUL?

Es innegable que las API RESTful HTTP han dominado la industria del software durante las últimas dos décadas. Permitieron la comunicación entre diferentes aplicaciones, servidores y dispositivos, lo que permitió que la web mundial y el ecosistema móvil crecieran y ofrecieran servicios esenciales para la mayoría de nosotros.

También es cierto que no ha sido un camino tranquilo. Una API RESTful es solo una API respetando el Transferencia de Estado Representacional (REST) restricciones de estilo arquitectónico. REST no es un protocolo ni un conjunto de herramientas. Interpretamos sus requisitos al diseñar e implementar una API para que sea RESTful. La dura verdad es que a menudo fallamos por dos razones principales: es difícil cumplir con todas las restricciones REST; Las opiniones varían de un desarrollador a otro y de un equipo a otro, lo que lleva a una implementación diferente que difícilmente puede interactuar.

Aquí es donde entra en juego gRPC.

gRPC es un sistema de llamada a procedimiento remoto de código abierto desarrollado por Google. Fue construido para reemplazar su infraestructura RPC interna llamada Stubby con la idea de aprovechar los estándares modernos como SPDY, HTTP / 2 y QUIC, y extender su aplicabilidad a los casos de uso de dispositivos móviles, IoT y la nube. Como puedes imaginar, aprovecha la inmensa experiencia y magnitud operativa de Google.

En este artículo, nos centraremos en algunos principios y requisitos de gRPC y los compararemos con REST destacando las ventajas y desventajas de ambas técnicas.

Diseño de API: recursos frente a servicios

La primera tarea al desarrollar una API RESTful es traducir su dominio en recursos identificados de forma única por una URL, por ejemplo, / usuario. Luego asignamos los verbos HTTP estándar POST, GET, PUT y DELETE a acciones CRUD: POST para creaciones, GET para recuperaciones, PUT para actualizaciones y upserts, y finalmente DELETE para eliminaciones.

El proceso de diseño es sencillo cuando se trata de CRUD, pero las cosas se complican cuando se trata de funciones comerciales más articuladas. ¿Cómo podemos bloquear a un usuario durante un período de tiempo determinado?

Este es el momento en que se iniciará la discusión dentro del equipo. Una opción sería agregar una propiedad lockUntil a nuestra representación de usuario y actualizar el usuario siempre que desee bloquearlo. Aún se preguntará si este enfoque es RESTful o si hay alguna opción mejor. La realidad es que se siente antinatural e innecesariamente complejo.

Veamos cómo funcionaríamos el mismo escenario en gRPC.

gRPC se basa en el principio fundamental de que debemos usar Servicios, no Objetos, y Mensajes, no Referencias. En otras palabras, en gRPC iniciamos el proceso definiendo nuestros servicios y sus tipos de mensajes. Usamos interfaces claras que se asignan directamente a nuestras funciones comerciales y conceptos de programación, como interfaces o métodos. Sin necesidad de traducciones. gRPC se trata de API, no de recursos. Nuestro servicio de cierre se puede definir fácilmente como:

message LockUserRequest {
	required string id = 1;
	required int32 minutes = 2;
}
message LockUserResponse {
	required int64 lockExpiry = 1;
}
service UserService {
	rpc LockUser(LockUserRequest) returns (LockUserResponse) {}
}

Figura 1: Definición de mensaje y servicio gRPC

Nota: Aprenda más sobre Tipos de API y diferentes protocolos API.

Definición de contrato

Al crear servicios web RESTful respaldados por un contrato formal, puede elegir entre dos enfoques: contrato último y contrato primero. Cuando se utiliza un enfoque de último contrato, los desarrolladores comienzan codificando la API en su idioma preferido y luego generan automáticamente un contrato a partir de él que sirve como documentación. Este es probablemente el enfoque más fácil para un equipo pequeño. Para equipos más grandes, el contrato primero suele ser más adecuado. En este caso, el contrato de la API se define por adelantado y algunas partes de la implementación se generan automáticamente a partir de él. Luego, cada equipo puede trabajar de forma independiente si se apega al contrato predefinido.

En ambos casos, tendrá que elegir qué idioma y conjunto de herramientas usar para definir y generar automáticamente contratos o código. Hay muchas opciones válidas como Swagger, ahora conocido como OpenAPI y RAML, pero una vez más no hay un estándar predefinido.

gRPC usa búfer de protocolo de forma predeterminada como su lenguaje de definición de interfaz (IDL) y conjunto de herramientas de serialización. Es estrictamente contrato primero.

Los servicios y mensajes se definen en archivos proto y los stubs relativos del servidor y del cliente se generan automáticamente utilizando el conjunto de herramientas gRPC en cualquiera de los lenguajes o plataformas compatibles: C ++, Java, Python, Go, Ruby, C #, Node.js, Android, Objective-C, PHP y Dart.

El servidor y los clientes podrán comunicarse sin problemas en todos los idiomas y plataformas. Puede crear fácilmente un servidor gRPC en Java con clientes en Go o Python. gRPC permite una interoperabilidad real desde el primer momento, algo que es increíblemente difícil de lograr entre las API RESTful implementadas por diferentes equipos.

/**
 * This services extends the base implementation {@link QuoteServiceImplBase} autogenerated by protoc.
 * @author marcol
 *
 */
public class QuoteService extends QuoteServiceImplBase {
	@Override
	public void getQuote(Empty request, StreamObserver responseObserver) {
		responseObserver.onNext(radomQuote());
		responseObserver.onCompleted();
	}
	private Quote randomQuote() {
		return Quote.newbuilder().setQuote()("Sample").build();
	}
}

Figura 2: Ejemplo de implementación del servidor gRPC

JSON frente a PROTOBUF

Si bien no existe una estrategia de serialización predefinida al desarrollar una API RESTful, JSON es el formato de carga útil más popular y puede considerarse el estándar de facto en la industria. Sus principales ventajas son que es fácil de leer y está bien soportado en la mayoría de idiomas.

{
  "name": "Marco",
  "nationality": "Italian"
  "age": 34 
}

Figura 3: Carga útil JSON de muestra

Las principales desventajas de JSON son que no se basa en esquemas, no tiene escritura y no tiene versiones. Todos estos inconvenientes conducen a problemas de serialización que pueden tener consecuencias desastrosas, especialmente en arquitecturas orientadas a microservicios donde las fallas caen en cascada. El conjunto de herramientas de serialización necesita procesar toda la carga útil e inferir el tipo de cada propiedad. No hace falta decir que es muy común que el tipo inferido sea incorrecto.
Los búferes de protocolo (protobufs) son, en cambio, el mecanismo de serialización neutral del lenguaje y la plataforma de Google. La estructura de datos se define por adelantado y las El conjunto de herramientas genera automáticamente el código para manejar la serialización y deserialización de forma gratuita. Está fuertemente tipado y es extensible..

message User {
  required string name = 1;
  optional string nationality = 2;
  optional int32 age = 3;
}

Figura 4: Ejemplo de definición de mensaje de usuario de gRPC

Como se puede ver, cada campo en la definición del mensaje tiene un tipo y un número único. Escribir elimina cualquier conjetura al serializar datos. Los números se utilizan para identificar los campos en el formato binario del mensaje y nunca deben cambiarse cuando se utilizan. Son el enfoque de Google para el control de versiones de API. Si tiene la intención de agregar nuevos campos en el futuro, simplemente les asignará un nuevo número. Los clientes y servidores existentes podrán identificar los campos que conocen por sus números e ignorar el resto.

Si queremos eliminar algunos campos de la definición del mensaje, debemos asegurarnos de que su nombre y número de campo no se reutilicen en el futuro utilizando la palabra clave reservada como se muestra arriba. Esto es necesario para garantizar la retrocompatibilidad al no permitir cambios de tipo para un nombre o número de campo determinado.

message User {
  reserved 4, 7 to 10;
  reserved "first_name", "last_name";
}

Figura 5: Uso de la palabra clave reservada

SINCRONIZACIÓN vs ASYNC

Las API RESTful de HTTP son síncronas y unarias de forma predeterminada, ya que se basan en el modelo de solicitud-respuesta disponible en HTTP / 1.x. El cliente envía una única solicitud y espera hasta recibir una respuesta. Sin embargo, las redes son intrínsecamente asincrónicas y propensas a fallar. En un diseño orientado a microservicios, una sola función empresarial puede traducirse en muchas solicitudes internas que afectan el rendimiento y aumentan la complejidad al obligar a los desarrolladores a encontrar formas inteligentes de hacer que sus sistemas sean resistentes.

Falla en cascada

Figura 6: falla en cascada en una arquitectura orientada a microservicios

gRPC admite el procesamiento asíncrono y síncrono al aprovechar al máximo HTTP / 2. Puede usarlo para realizar RPC Unary, RPC de transmisión de servidor, RPC de transmisión de cliente o RPC bidireccional. Prácticamente cubre todos los casos de uso imaginables, desde la operación CRUD simple hasta la transmisión de alto rendimiento utilizada en sistemas de almacenamiento y contextos de big data que requieren el mejor rendimiento posible.

Hagamos evolucionar un simple servicio Unary RPC en un flujo bidireccional más potente.

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

Figura 7: Definición de servicio RPC unario

Esto es bastante básico. Nuestro rpc SayHello espera un solo HelloRequest y devuelve un solo HelloResponse.

Si quisiéramos definir Server Streaming RPC, simplemente cambiaríamos HelloResponse a una secuencia.

service HelloService {
  rpc LotsOfReplies (HelloRequest) returns (stream HelloResponse);
}

Figura 8: Definición del servicio RPC de transmisión por secuencias del servidor

De manera similar, si quisiéramos cambiar a Client Streaming RPC, cambiaríamos HelloRequest a una transmisión.

service HelloService {
  rpc LotsOfGreetings (stream HelloRequest) returns (HelloResponse);
}

Figura 9: Definición del servicio Client Streaming RPC

Finalmente, podemos utilizar corrientes en ambas direcciones.

service HelloService {
  rpc BiDirectionalHello (stream HelloRequest) returns (stream HelloResponse);
}

Figura 10: Definición del servicio RPC de transmisión bidireccional

Auth

El estilo arquitectónico REST no brinda orientación sobre cuestiones de autenticación y autorización. Durante años, las API se han protegido mediante esquemas de autenticación personalizados e ineficientes. Últimamente, la llegada de SAML 2.0, OAuth 2.0 y OpenID Connect 1.0 facilitaron el proceso de estandarización mejorando la seguridad y la interoperabilidad entre diferentes proveedores. 

gRPC tiene autenticación conectable totalmente integrada. Tiene dos estrategias de autenticación integradas: basado en certificado que hace uso de certificados SSL / TLS; y basado en token que aprovecha los tokens de Google OAuth 2.0. También es extremadamente fácil ampliar el soporte de gRPC para otros mecanismos de autenticación. Su API de autenticación gira en torno al concepto unificado de un objeto Credentials. Luego, los desarrolladores pueden aprovechar la API del complemento de credenciales para ingresar su propio tipo de credenciales.

Rendimiento

gRPC está diseñado para un diseño de aplicaciones distribuidas de alto rendimiento y alta productividad. El rendimiento es un principio clave en gRPC y está garantizado por evaluaciones comparativas continuas como parte de su flujo de trabajo de desarrollo. Las pruebas de rendimiento en varios idiomas se ejecutan cada hora en la sucursal principal y los resultados se publican en https://grpc.io/docs/guides/benchmarking.html 

Panel de rendimiento de gRPC

Figura 11: panel de rendimiento de gRPC

Cerrando la brecha: puerta de enlace gRPC-REST

Los protobufs no son legibles por humanos. Este es posiblemente el mayor inconveniente de gRPC. Los desarrolladores y especialistas en garantía de calidad que se utilizan para inspeccionar las cargas útiles de respuesta y solicitud HTTP a través del cable deben cambiar el enfoque de depuración. Además, no es posible invocar un punto final simplemente escribiendo su dirección en el navegador.

Invocar API RESTFUL

Figura 12: Invocación de una API RESTful desde el navegador

Esta no es una preocupación menor. La simplicidad es un aspecto clave de REST. Probablemente, ha sido la razón principal de su gran popularidad y creo que será la razón por la que REST seguirá dominando el espacio API en un futuro próximo.

Estoy seguro de que ahora te estás confundiendo. Hasta ahora, hemos alabado a gRPC, pero ahora decimos que las API RESTful llegaron para quedarse.

Bueno, es un hecho. No se puede simplemente cambiar de un día para otro a una nueva tecnología. Hay varias razones por las que no haría eso. Necesita mantener la compatibilidad con versiones anteriores y es posible que haya invertido tiempo y dinero para crear un ecosistema de herramientas y tecnologías que giran en torno a las API RESTful. Además, ¿sus clientes están listos o interesados ​​en cambiarse a gRPC?

Afortunadamente, los desarrolladores de gRPC son muy conscientes de esto e incorporaron el concepto de Regla HTTP en la definición del servicio. HttpRule define la asignación de un método RPC a uno o más HTTP REST API métodos. La asignación especifica cómo se asignan las diferentes partes del mensaje de solicitud RPC a la ruta de URL, los parámetros de consulta de URL y el cuerpo de la solicitud HTTP.

service Messaging {
  rpc GetMessage(GetMessageRequest) returns (Message) {
    option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}";
  }
}
message GetMessageRequest {
  message SubMessage {
    string subfield = 1;
  }
  string message_id = 1; // mapped to the URL
  SubMessage sub = 2;    // `sub.subfield` is url-mapped
}
message Message {
  string text = 1; // content of the resource
}

Figura 13: Definición de HttpRule en un servicio gRPC

La misma anotación http se puede expresar alternativamente dentro del archivo YAML de configuración de API de GRPC.

http:
  rules:
    - selector: .Messaging.GetMessage
      get: /v1/messages/{message_id}/{sub.subfield}

Figura 14: Definición de HttpRule en un archivo YAML de configuración de gRPC

Esta definición permite una asignación bidireccional automática de HTTP JSON a RPC.

Finalmente, la comunidad gRPC ha creado el puerta de enlace grpc plugin para protobuf, la herramienta generadora de código protobuf. Lee la definición del servicio gRPC con sus opciones HTTP personalizadas y automatiza la provisión de las API en estilo gRPC y RESTful al mismo tiempo.

Diagrama de puerta de enlace de gRPC

Figura 15: diagrama de alto nivel de la puerta de enlace de gRPC

Obtienes lo mejor de ambos mundos con poco esfuerzo. Esto es particularmente útil para los servicios expuestos a la World Wide Web. Puede seguir aprovechando las API HTTP RESTful para las integraciones de sus clientes, mientras traduce sus solicitudes a gRPC para la comunicación interna.

Cloud Native y Kubernetes

Las tecnologías Cloud Native han conquistado la industria en la última década. No hay tecnología que pueda sobrevivir a la feroz competencia sin unirse al tren de Cloud Native. gRPC no es una excepción y es un proyecto en incubación alojado por el Fundación de computación nativa en la nube (CNCF). Cuenta con un gran apoyo de la comunidad de código abierto en GitHub y es utilizado por líderes de la industria como Netflix, Cisco, Juniper y CoreOS. Sin embargo, la integración de gRPC con el omnipresente Kubernetes no es tan sencilla como muchos esperarían. El motivo es que el tráfico de gRPC no se equilibra correctamente en varios pods asociados con el mismo servicio. gRPC solicita el equilibrador de carga Kubernetes  

Figura 16: Las solicitudes no tienen equilibrio de carga como se esperaba en una configuración de Kubernetes

La razón por la que las solicitudes no tienen un balance de carga correcto desde el primer momento es simplemente HTTP / 2. gRPC se basa en HTTP / 2 que, como sabemos, está destinado a reducir la latencia al multiplexar varias solicitudes a través de una sola conexión TCP. Lo que sucede en el diagrama anterior es que el cliente de gRPC en el Pod A abre una conexión TCP en su primera solicitud y la está reutilizando para las siguientes. Si bien este comportamiento reduce la latencia, no es deseable en un sistema redundante y de alta disponibilidad en el que esperamos distribuir la carga en varias instancias del mismo componente.

Una solución casera al problema sería administrar múltiples conexiones en nuestra aplicación cliente. Sin embargo, siempre recomiendo encarecidamente no agregar tales preocupaciones en nuestra aplicación. La mejor manera de lidiar con este problema es emplear una de las mallas de servicios líderes, como Istio y Linkerd. Ambos tienen soporte completo para HTTP / 2, por lo tanto, gRPC.

Sidecar gRPC solicita el equilibrador de carga Kubernetes

Figura 17: El sidecar de la malla de servicios restaura el equilibrio de carga adecuado de las solicitudes de gRPC

Una malla de servicios generalmente funciona agregando un contenedor sidecar dentro de los pods que albergan nuestras aplicaciones. El contenedor sidecar se hace cargo de la configuración de red del Pod e inspecciona todo el tráfico entrante y saliente. De esta manera, la malla de servicios puede controlar dónde se envían las solicitudes y, por lo tanto, volver a introducir el comportamiento natural de equilibrio de carga que esperamos en tales configuraciones. Lo mejor es que no tenemos que agregar una sola línea de código o configuración en nuestras aplicaciones. Todo lo que se necesita es agregar la malla en nuestro clúster de Kubernetes.

Conclusión

Los búferes de protocolo y gRPC son la respuesta de Google a los requisitos del sistema moderno. Permiten una comunicación fluida entre diferentes aplicaciones, lenguajes y plataformas. Diferentes equipos pueden acordar un contrato y trabajar de forma independiente mientras esperan una integración fluida garantizada por el tipado sólido de protobufs y el soporte multiplataforma. gRPC proporciona un rendimiento superior que se supervisa continuamente con pruebas cada hora. Finalmente, gRPC está centrado en API. Sus definiciones de servicio se asignan directamente a los métodos del programa y no requieren una traducción extraña a un modelo basado en recursos.

Sin embargo, gRPC no está destinado a reemplazar las API RESTful. Al menos no ahora. La simplicidad de REST y su vasto ecosistema no se pueden sustituir de la noche a la mañana, especialmente en las API de cara al público. Afortunadamente, esto no es un problema ya que la comunidad gRPC ha proporcionado medios para unir ambos mundos con poco esfuerzo con la introducción de grpc-gateway.

En conclusión, gRPC es una tecnología prometedora que ya ha ganado una huella significativa en el espacio API. El propio Google se ha comprometido a ofrecer pronto todas sus API públicas tanto de forma RESTful como gRPC. Si aún no le ha dado una oportunidad a gRPC, definitivamente es hora de familiarizarse con él. Esta tecnología es particularmente adecuada para arquitecturas orientadas a microservicios donde el rendimiento y la confiabilidad de la intercomunicación de aplicaciones es clave para el éxito del sistema.