¿Qué es GraphQL?

Zexuan Luo

Zexuan Luo

November 4, 2022

Technology

GraphQL es un lenguaje de manipulación de consultas orientado a API, lanzado por Facebook en 2015. A diferencia de otros diseños de API, GraphQL permite a los clientes formar declaraciones de consulta basadas en una estructura de datos previamente acordada y permite que el servidor analice la declaración y devuelva solo lo que se necesita. De esta manera, GraphQL proporciona riqueza y flexibilidad, evitando al mismo tiempo la pérdida de rendimiento causada por datos redundantes, lo que convierte a GraphQL en una excelente opción para aplicaciones que requieren manejar muchos objetos de datos complejos.

En 2018, GraphQL lanzó una especificación completa y una versión estable. Ese mismo año, Facebook donó el proyecto GraphQL a la GraphQL Foundation bajo la Linux Foundation. Desde entonces, GraphQL ha sido adoptado en muchos proyectos de código abierto y organizaciones comerciales. Hasta ahora, existen varias implementaciones principales de GraphQL en el lado del cliente en el mercado. Las implementaciones en el lado del servidor están disponibles en todos los principales lenguajes de programación del lado del servidor, e incluso en lenguajes menos comunes como D y R.

Algunos escenarios reales y desafíos para GraphQL

El ejemplo más conocido de GraphQL es la API GraphQL de GitHub.

Antes de adoptar GraphQL, GitHub proporcionaba una API REST para exponer los ricos datos generados por millones de proyectos alojados, lo cual fue tan exitoso que se convirtió en un modelo a seguir al diseñar APIs REST. Sin embargo, a medida que el número de objetos de datos creció y los campos dentro de los objetos se volvieron más grandes, la API REST comenzó a revelar cada vez más desventajas. En el lado del servidor, GitHub tuvo que establecer límites estrictos en la frecuencia de las llamadas para reducir costos debido a la cantidad de datos generados con cada llamada. En el lado de los desarrolladores, tuvieron que lidiar con esta limitación, ya que, aunque una sola llamada devuelve muchos datos, la mayoría de ellos son inútiles. Para obtener una pieza específica de información, los desarrolladores a menudo necesitan lanzar múltiples consultas y luego escribir mucho código de unión para combinar los datos significativos de los resultados de las consultas en el contenido deseado. En el proceso, también tienen que lidiar con las limitaciones de "número de llamadas".

Por lo tanto, GitHub adoptó GraphQL tan pronto como salió. GitHub se convirtió en el "embajador" de GraphQL, llevando su aplicación a miles de desarrolladores. La API GraphQL es ahora la opción principal de GitHub. Desde el primer anuncio de soporte para GraphQL, GitHub ha publicado varios artículos sobre GraphQL cada año. Para permitir que los desarrolladores migren a GraphQL, GitHub ha escrito una aplicación de consulta interactiva específicamente para este propósito: https://docs.github.com/en/graphql/overview/explorer. Los desarrolladores pueden aprender a escribir GraphQL a través de esta aplicación.

Sin embargo, GraphQL no es una panacea. Recientemente, GitHub dejó de usar su propia implementación de GraphQL para la API de paquetes. Muchas personas también han comenzado a discutir algunas de las desventajas de GraphQL. Muchos de los problemas con GraphQL surgen del hecho de que su estructura es tan diferente del estándar HTTP que no hay una manera fácil de mapear algunos de los conceptos de GraphQL en una estructura como la ruta/cabecera HTTP. Tratar GraphQL como una API HTTP normal requiere trabajo de desarrollo adicional. Como resultado, los desarrolladores que desean gestionar sus propias APIs GraphQL tendrán que usar una puerta de enlace API habilitada para GraphQL.

Cómo APISIX soporta GraphQL

Actualmente, APISIX soporta enrutamiento dinámico a través de algunas propiedades de GraphQL. Con esta capacidad, podemos aceptar solo solicitudes GraphQL específicas o hacer que diferentes GraphQLs se reenvíen a diferentes upstreams.

Tomemos la siguiente declaración GraphQL como ejemplo:

query getRepo {
    owner {
        name
    }
    repo {
        created
    }
}

APISIX extrae las siguientes tres propiedades de GraphQL para el enrutamiento:

  • graphql_operation
  • graphql_name
  • graphql_root_fields

En la declaración GraphQL anterior:

  • graphql_operation corresponde a query
  • graphql_name corresponde a getRepo
  • graphql_root_fields corresponde a ["owner", "repo"]

Creemos una ruta para demostrar las capacidades de enrutamiento granular de APISIX para GraphQL:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "methods": ["POST"],
    "uri": "/graphql",
    "vars": [
        ["graphql_operation", "==", "query"],
        ["graphql_name", "==", "getRepo"],
        ["graphql_root_fields", "has", "owner"]
    ],
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:2022": 1
        }
    }
}'

A continuación, usa una solicitud con una declaración GraphQL para acceder:

curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    owner {
        name
    }
    repo {
        created
    }
}'
HTTP/1.1 200 OK
...

Podemos ver que la solicitud llegó al upstream ya que la declaración de consulta coincidió con las tres condiciones.

Por el contrario, si accedemos con una declaración que no coincide, por ejemplo, no incluye el campo owner:

curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    repo {
        created
    }
}'
HTTP/1.1 404 Not Found
...

No coincidirá con la regla de enrutamiento correspondiente.

Podemos crear adicionalmente una ruta que permita que las declaraciones que no contienen un campo owner se enruten a otro upstream:

curl http://127.0.0.1:9180/apisix/admin/routes/2 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "methods": ["POST"],
    "uri": "/graphql",
    "vars": [
        ["graphql_operation", "==", "query"],
        ["graphql_name", "==", "getRepo"],
        ["graphql_root_fields", "!", "has", "owner"]
    ],
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "192.168.0.1:2022": 1
        }
    }
}'
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    repo {
        created
    }
}'
HTTP/1.1 200 OK
...

Perspectivas del futuro soporte de APISIX para GraphQL

Además del enrutamiento dinámico, APISIX también podría introducir más operaciones basadas en campos específicos de GraphQL en el futuro. Por ejemplo, la API GraphQL de GitHub tiene una fórmula específica para la limitación de tasa, y podemos aplicar reglas similares para convertir una sola solicitud GraphQL en un número correspondiente de "llamadas virtuales" para lograr una limitación de tasa específica de GraphQL.

También podemos pensar en el problema de otra manera. La aplicación en sí todavía proporciona la API REST, y la puerta de enlace convierte las solicitudes GraphQL en solicitudes REST y las respuestas REST en respuestas GraphQL en el nivel más externo. La API GraphQL proporcionada de esta manera puede realizar funciones como RBAC, limitación de tasa, almacenamiento en caché, etc., sin desarrollar complementos especiales. Desde un punto de vista técnico, esta idea no es tan difícil de implementar. Después de todo, en 2022, incluso las APIs REST tienden a proporcionar especificaciones OpenAPI como esquema, lo cual es solo una transferencia entre el esquema GraphQL y el esquema OpenAPI, más el filtrado de campos específico de GraphQL. (Por supuesto, debo admitir que no lo he practicado yo mismo. Tal vez haya desafíos en algunos detalles que aún no se han superado).

Los lectores cuidadosos notarán que las APIs GraphQL convertidas de esta manera solo pueden operar en un modelo a la vez, lo que obviamente no cumple con los requisitos de flexibilidad de GraphQL y no es más que una API REST vestida de GraphQL. Sin embargo, GraphQL tiene un concepto llamado unión de esquemas que permite a los implementadores combinar múltiples esquemas.

Como ejemplo, tenemos dos APIs, una llamada GetEvent y otra llamada GetLocation, que devuelven los tipos Event y Location respectivamente.

type Event {
    id: string
    location_id: string
}

type Location {
    id: string
    city: string
}

type Query {
    GetEvent(id: string): Event
    GetLocation(id: string): Location
}

Podemos agregar una configuración que combine estas dos APIs en una nueva API llamada GetEventWithLocation, que se ve así:

type EventWithLocation {
    id: string
    location: Location
}

type Query {
    GetEventWithLocation(id: string): EventWithLocation
}

La implementación específica de la unión la realiza la puerta de enlace. En el ejemplo anterior, la puerta de enlace divide la API en dos, llamando a GetEvent para obtener el location_id y luego a GetLocation para obtener los datos combinados.

En resumen, al convertir REST a GraphQL, cada API REST puede convertirse en un modelo GraphQL correspondiente; y con la ayuda de la unión de esquemas, múltiples modelos pueden combinarse en una API GraphQL. De esta manera, podemos construir una API GraphQL rica y flexible sobre la API REST existente y gestionar complementos específicos a nivel de la API REST. Este diseño incidentalmente resuelve algunos de los problemas de orquestación de APIs. Como en el ejemplo anterior, tomamos la salida de una API (Event.location_id) como la entrada de otra API (Location.id).

Tags: