in ,

Implementación de un servidor GraphQL con componentes en PHP

GraphQL es un lenguaje de consulta para API, que brinda a los clientes el poder de pedir exactamente qué datos necesitan y recibir exactamente eso, ni más ni menos. De esta manera, una sola consulta ya puedes obtener todos los datos necesarios para representar un componente.

(Una API REST, en comparación, debe desencadenar varios viajes de ida y vuelta para obtener datos de múltiples recursos de diferentes puntos finales, lo que puede llegar a ser muy lento, especialmente en dispositivos móviles).

Aunque GraphQL (que significa «Lenguaje de consulta de gráficos») utiliza un modelo de datos de gráficos para representar datos, el servidor de GraphQL no necesariamente necesita usar un gráfico como estructura de datos para resolver la consulta, pero puedes usar cualquier estructura de datos que desees. El gráfico es solo un modelo mental, no una implementación real.

Esto está respaldado por el proyecto GraphQL al indicar en tu sitio web graphql.org (énfasis mío):

Los gráficos son herramientas poderosas para modelar muchos fenómenos del mundo real porque se asemejan a nuestros modelos mentales naturales y descripciones verbales del proceso subyacente. Con GraphQL, tú modelas tu dominio de negocio como un gráfico mediante la definición de un esquema; dentro de tu esquema, define diferentes tipos de nodos y cómo se conectan/relacionan entre sí. En el cliente, esto crea un patrón similar a la programación orientada a objetos: tipos que hacen referencia a otros tipos. En el servidor, dado que GraphQL solo define la interfaz, tiene la libertad de usarla con cualquier backend (¡nuevo o heredado!).

Esta es una buena noticia, porque tratar con gráficos o árboles (que son subconjuntos de gráficos) no es trivial, y puede conducir a una complejidad de tiempo exponencial o logarítmica para resolver la consulta (es decir, el tiempo requerido para resolver una consulta puede aumentar varios órdenes de magnitud por cada nueva entrada a la consulta).

En este artículo, describiré el diseño arquitectónico del servidor GraphQL en PHP GraphQL por PoP, que utiliza componentes como una estructura de datos en lugar de gráficos. Este servidor debe su nombre a PoP, la librería para construir componentes en PHP sobre la que se basa. (Soy el autor de ambos proyectos.)

Este artículo está dividido en 5 secciones, explicando:

  1. Qué es un componente
  2. Cómo funciona PoP
  3. Cómo se definen los componentes en PoP
  4. Cómo los componentes son naturalmente adecuados para GraphQL
  5. El rendimiento del uso de componentes para resolver una consulta de GraphQL

Empecemos.

1. Qué es un componente

El diseño de cada página web se puede representar utilizando componentes. Un componente es simplemente un conjunto de piezas de código (como HTML, JavaScript y CSS) juntas para crear una entidad autónoma, que puede envolver otros componentes para crear estructuras más complejas y ser envuelta por otros componentes también. Cada componente tiene un propósito, que puede ir desde algo muy básico, como un enlace o un botón, hasta algo muy elaborado, como un carrusel o un cargador de imágenes de arrastrar y soltar.

Construir un sitio a través de componentes es similar a jugar con LEGO. Por ejemplo, en la página web de la imagen de abajo, se componen componentes simples (enlaces, botones, avatares) para crear estructuras más complejas (widgets, secciones, barras laterales, menús) hasta la parte superior, hasta obtener la página web:

La página es un componente que envuelve componentes envolviendo componentes, como se muestra en los cuadrados

Los componentes se pueden implementar tanto para el lado del cliente (como las bibliotecas JS Vue y React, o las bibliotecas de componentes CSS Bootstrap y Material-UI) como para el lado del servidor, en cualquier idioma.

2. Cómo funciona PoP

PoP describe una arquitectura basada en un modelo de componentes del lado del servidor y lo implementa en PHP a través de la biblioteca de modelos de componentes.

En las secciones siguientes, los términos «componente» y «módulo» se utilizan indistintamente.

La jerarquía de componentes

La relación de todos los módulos que se envuelven entre sí, desde el módulo más alto hasta el último nivel, se denomina jerarquía de componentes. Esta relación se puede expresar a través de una matriz asociativa (una matriz de =>) en el lado del servidor, en la que cada módulo indica su nombre como el atributo clave y tus módulos internos bajo la propiedad. keyproperty"modules"

Los datos en la matriz PHP también se pueden usar directamente en el lado del cliente, codificados como un objeto JSON.

La jerarquía de componentes tiene este aspecto:

$componentHierarchy = [
  'module-level0' => [
    "modules" => [
      'module-level1' => [
        "modules" => [
          'module-level11' => [
            "modules" => [...]
          ],
          'module-level12' => [
            "modules" => [
              'module-level121' => [
                "modules" => [...]
              ]
            ]
          ]
        ]
      ],
      'module-level2' => [
        "modules" => [
          'module-level21' => [
            "modules" => [...]
          ]
        ]
      ]
    ]
  ]
]

La relación entre los módulos se define de una manera estrictamente descendente: un módulo envuelve otros módulos y sabe quiénes son, pero no sabe, y no le importa, qué módulos lo están envolviendo.

Por ejemplo, en la jerarquía de componentes anterior, el módulo sabe que envuelve módulos y, y, transitivamente, también sabe que envuelve; pero al módulo no le importa quién lo está envolviendo, por lo tanto no es consciente de.'module-level1''module-level11''module-level12''module-level121''module-level11''module-level1'

Al tener la estructura basada en componentes, agregamos la información real requerida por cada módulo, que se clasifica en configuraciones (como valores de configuración y otras propiedades) y datos (como los ID de los objetos de base de datos consultados y otras propiedades), y se coloca en consecuencia en las entradas modulesettings y moduledata:

$componentHierarchyData = [
  "modulesettings" => [
    'module-level0' => [
      "configuration" => [...],
      ...,
      "modules" => [
        'module-level1' => [
          "configuration" => [...],
          ...,
          "modules" => [
            'module-level11' => [
              ...children...
            ],
            'module-level12' => [
              "configuration" => [...],
              ...,
              "modules" => [
                'module-level121' => [
                  ...children...
                ]
              ]
            ]
          ]
        ],
        'module-level2' => [
          "configuration" => [...],
          ...,
          "modules" => [
            'module-level21' => [
              ...children...
            ]
          ]
        ]
      ]
    ]
  ],
  "moduledata" => [
    'module-level0' => [
      "dbobjectids" => [...],
      ...,
      "modules" => [
        'module-level1' => [
          "dbobjectids" => [...],
          ...,
          "modules" => [
            'module-level11' => [
              ...children...
            ],
            'module-level12' => [
              "dbobjectids" => [...],
              ...,
              "modules" => [
                'module-level121' => [
                  ...children...
                ]
              ]
            ]
          ]
        ],
        'module-level2' => [
          "dbobjectids" => [...],
          ...,
          "modules" => [
            'module-level21' => [
              ...children...
            ]
          ]
        ]
      ]
    ]
  ]
]

A continuación, los datos del objeto de base de datos se agregan a la jerarquía de componentes. Esta información no se coloca debajo de cada módulo, sino en una sección compartida llamada, para evitar duplicar la información cuando 2 o más módulos diferentes obtienen los mismos objetos de la base de datos .databases

Además, la biblioteca representa los datos del objeto de la base de datos de una manera relacional, para evitar duplicar la información cuando 2 o más objetos de base de datos diferentes están relacionados con un objeto común (como 2 publicaciones que tienen el mismo autor).

En otras palabras, los datos de objetos de base de datos se normalizan. La estructura es un diccionario, organizado bajo cada tipo de objeto primero y el ID de objeto segundo, del que podemos obtener las propiedades del objeto:

$componentHierarchyData = [
  ...
  "databases" => [
    "dbobject_type" => [
      "dbobject_id" => [
        "property" => ...,
        ...
      ],
      ...
    ],
    ...
  ]
]

Por ejemplo, el objeto a continuación contiene una jerarquía de componentes con dos módulos, =>, donde el módulo obtiene publicaciones de blog. Ten en cuenta lo siguiente: "page""post-feed""post-feed"

  • Cada módulo sabe cuáles son sus objetos consultados desde la propiedad (ID y para las entradas de blog) dbobjectids49
  • Cada módulo conoce el tipo de objeto para sus objetos consultados desde la propiedad
  • Dado que los datos del objeto de base de datos son relacionales, la propiedad contiene el identificador del objeto de autor en lugar de imprimir los datos de autor directamente "author"
$componentHierarchyData = [
  "moduledata" => [
    'page' => [
      "modules" => [
        'post-feed' => [
          "dbobjectids": [4, 9]
        ]
      ]
    ]
  ],
  "modulesettings" => [
    'page' => [
      "modules" => [
        'post-feed' => [
          "dbkeys" => [
            'id' => "posts",
            'author' => "users"
          ]
        ]
      ]
    ]
  ],
  "databases" => [
    'posts' => [
      4 => [
        'title' => "Hello World!",
        'author' => 7
      ],
      9 => [
        'title' => "Everything fine?",
        'author' => 7
      ]
    ],
    'users' => [
      7 => [
        'name' => "Leo"
      ]
    ]
  ]
]

Carga de datos

Cuando un módulo muestra una propiedad de un objeto de base de datos, es posible que el módulo no sepa o no le importe qué objeto es; todo lo que le importa es definir qué propiedades del objeto cargado se requieren.

Por ejemplo, considera la imagen a continuación: un módulo carga un objeto de la base de datos (en este caso, una sola publicación), y luego sus módulos descendientes mostrarán ciertas propiedades del objeto, como "title" y "content":

Mientras que algunos módulos cargan el objeto de base de datos, otros cargan propiedades

Por lo tanto, a lo largo de la jerarquía de componentes, los módulos de «carga de datos» se encargarán de cargar los objetos consultados (el módulo carga el puesto único, en este caso), y sus módulos descendientes definirán qué propiedades del objeto DB se requieren ("title" y "content", en este caso)

La obtención de todas las propiedades requeridas para el objeto de base de datos se puede hacer atravesando la jerarquía de componentes: a partir del módulo de carga de datos, PoP itera todos tus módulos descendientes hasta llegar a un nuevo módulo de carga de datos, o hasta el final del árbol; en cada nivel obtienes todas las propiedades requeridas y, a continuación, combina todas las propiedades y las consulta desde la base de datos, todas ellas una sola vez.

Debido a que los datos de los objetos de base de datos se recuperan de manera relacional, también podemos aplicar esta estrategia entre las relaciones entre los propios objetos de base de datos.

Considera la imagen a continuación: comenzando desde el tipo de objeto «post» y bajando la jerarquía de componentes, tendremos que cambiar el tipo de objeto de base de datos a «user» y «comment», correspondiente al autor de la publicación y a cada uno de los comentarios de la publicación respectivamente, y luego, para cada comentario, debe cambiar el tipo de objeto una vez más a «user» para corresponder al autor del comentario. Pasar de un objeto de base de datos a un objeto relacional es lo que yo llamo «cambiar de dominio».

Después de cambiar a un nuevo dominio, desde ese nivel en la jerarquía de componentes hacia abajo, todas las propiedades requeridas estarán sujetas al nuevo dominio: la propiedad «name» se obtiene del objeto «user» que representa al autor de la publicación, «content» del objeto «comment» que representa cada uno de los comentarios de la publicación y, a continuación «name», del objeto «user» que representa al autor de cada comentario:

Cambiar el objeto de base de datos de un dominio a otro

Al atravesar la jerarquía de componentes, PoP sabes cuándo estás cambiando de dominio y, de manera adecuada, obtienes los datos de objetos relacionales.

3. Cómo se definen los componentes en PoP

Las propiedades del módulo (valores de configuración, qué datos de base de datos obtener, etc.) y los módulos descendientes se definen a través de objetos ModuleProcessor, módulo por módulo, y PoP crea la jerarquía de componentes a partir de todos los módulos ModuleProcessor involucrados.

Similar a una aplicación React (donde debemos indicar en qué componente se representa <div id=»root»></div>), el modelo de componentes en PoP debe tener un módulo de entrada. A partir de él, PoP recorrerá todos los módulos de la jerarquía de componentes, ModuleProcessor obtendrá las propiedades de cada uno de los correspondientes y creará la matriz asociativa anidada con todas las propiedades de todos los módulos.

Cuando un componente define un componente descendiente, hace referencia a él a través de una matriz de 2 partes:

  1. La clase PHP
  2. El nombre del componente

Esto es así porque los componentes suelen compartir propiedades. Por ejemplo, los componentes POST_THUMBNAIL_LARGE y POST_THUMBNAIL_SMALL compartirán la mayoría de las propiedades, con la excepción del tamaño de la miniatura. Entonces, tiene sentido agrupar todos los componentes similares bajo una misma clase php y usar instrucciones switch para identificar el módulo solicitado y devolver la propiedad correspondiente.

Un ModuleProcessor para que los componentes del widget de publicación se coloquen en diferentes páginas se ven así:

class PostWidgetModuleProcessor extends AbstractModuleProcessor {
 
  const POST_WIDGET_HOMEPAGE = 'post-widget-homepage';
  const POST_WIDGET_AUTHORPAGE = 'post-widget-authorpage';
 
  function getSubmodulesToProcess() {
   
    return [
      self::POST_WIDGET_HOMEPAGE,
      self::POST_WIDGET_AUTHORPAGE,
    ];
  }
 
  function getSubmodules($module): array
  {
    $ret = [];
 
    switch ($module[1]) {     
      case self::POST_WIDGET_HOMEPAGE:
      case self::POST_WIDGET_AUTHORPAGE:
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_THUMB
        ];
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_TITLE
        ];
        break;
    }
    switch ($module[1]) {     
      case self::POST_WIDGET_HOMEPAGE:
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_DATE
        ];
        break;
    }
 
    return $ret;
  }
 
  function getImmutableConfiguration($module, &$props)
  {
    $ret = [];
 
    switch ($module[1]) {
      case self::POST_WIDGET_HOMEPAGE:       
        $ret['description'] = __('Latest posts', 'my-domain');
        $ret['showmore'] = $this->getProp($module, $props, 'showmore');
        $ret['class'] = $this->getProp($module, $props, 'class');
        break;
 
      case self::POST_WIDGET_AUTHORPAGE:       
        $ret['description'] = __('Latest posts by the author', 'my-domain');
        $ret['showmore'] = false;
        $ret['class'] = 'text-center';
        break;
    }
 
    return $ret;
  }
   
  function initModelProps($module, &$props)
  {
    switch ($module[1]) {
      case self::POST_WIDGET_HOMEPAGE:
        $this->setProp($module, $props, 'showmore', false);
        $this->appendProp($module, $props, 'class', 'text-center');
        break;
    }
 
    parent::initModelProps($module, $props);
  }
  // ...
}

La creación de componentes reutilizables se logra mediante la creación de clases abstractas ModuleProcessor que definen funciones de marcador de posición que deben ser implementadas por alguna clase de instancia:

abstract class PostWidgetLayoutAbstractModuleProcessor extends AbstractModuleProcessor
{
  function getSubmodules($module): array
  { 
    $ret = [
      $this->getContentModule($module),
    ];
 
    if ($thumbnail_module = $this->getThumbnailModule($module))
    {
      $ret[] = $thumbnail_module;
    }
 
    if ($aftercontent_modules = $this->getAfterContentModules($module))
    {
      $ret = array_merge(
        $ret,
        $aftercontent_modules
      );
    }
 
    return $ret;
  }
 
  abstract protected function getContentModule($module): array;
 
  protected function getThumbnailModule($module): ?array
  {
    // Default value (overridable)
    return [self::class, self::THUMBNAIL_LAYOUT];
  }
 
  protected function getAfterContentModules($module): array
  {
    return [];
  }
 
  function getImmutableConfiguration($module, &$props): array
  {
    return [
      'description' => $this->getDescription(),
    ];
  }
 
  protected function getDescription($module): string
  {
    return '';
  }
}

Las clases personalizadas ModuleProcessor pueden extender la clase abstracta y definir sus propias propiedades:

class PostLayoutModuleProcessor extends AbstractPostLayoutModuleProcessor {
 
  const POST_CONTENT = 'post-content'
  const POST_EXCERPT = 'post-excerpt'
  const POST_THUMBNAIL_LARGE = 'post-thumbnail-large'
  const POST_THUMBNAIL_MEDIUM = 'post-thumbnail-medium'
  const POST_SHARE = 'post-share'
 
  function getSubmodulesToProcess() {
   
    return [
      self::POST_CONTENT,
      self::POST_EXCERPT,
      self::POST_THUMBNAIL_LARGE,
      self::POST_THUMBNAIL_MEDIUM,
      self::POST_SHARE,
    ];
  }
 
}
 
class PostWidgetLayoutModuleProcessor extends AbstractPostWidgetLayoutModuleProcessor
{
  protected function getContentModule($module): ?array
  {
    switch ($module[1])
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_CONTENT
        ];
 
      case self::POST_WIDGET_HOMEPAGE_MEDIUM:
      case self::POST_WIDGET_HOMEPAGE_SMALL:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_EXCERPT
        ];
    }
 
    return parent::getContentModule($module);
  }
 
  protected function getThumbnailModule($module): ?array
  {
    switch ($module[1])
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_THUMBNAIL_LARGE
        ];
 
      case self::POST_WIDGET_HOMEPAGE_MEDIUM:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_THUMBNAIL_MEDIUM
        ];
    }
 
    return parent::getThumbnailModule($module);
  }
 
  protected function getAfterContentModules($module): array
  {
    $ret = [];
 
    switch ($module[1])
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        $ret[] = [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_SHARE
        ];
        break
    }
 
    return $ret;
  }
 
  protected function getDescription($module): string
  {
    return __('These are my blog posts', 'my-domain');
  }
}

4. Cómo los componentes son naturalmente adecuados para GraphQL

El modelo de componentes puede asignar naturalmente una consulta GraphQL en forma de árbol, lo que lo convierte en una arquitectura ideal para implementar un servidor GraphQL.

GraphQL de PoP ha implementado las clases ModuleProcessor necesarias para transformar una consulta GraphQL en tu jerarquía de componentes correspondiente y resolverla utilizando el motor de carga de datos PoP.

Esta es la razón y cómo funciona esta solución.

Asignación de componentes del lado cliente a consultas de GraphQL

La consulta GraphQL se puede representar utilizando la jerarquía de componentes de PoP, en la que cada tipo de objeto representa un componente y cada campo de relación de un tipo de objeto a otro tipo de objeto representa un componente que envuelve otro componente.

Veamos cómo es éste el caso usando un ejemplo. Digamos que queremos crear el siguiente widget «Director destacado»:

Widget de director destacado

Usando Vue o React (o cualquier otra biblioteca basada en componentes), primero identificaríamos los componentes. En este caso, tendríamos un componente externo <FeaturedDirector> (en rojo), que envuelve un componente <Film> (en azul), que a su vez envuelve un componente <Actor> (en verde):

Identificación de componentes en el widget

El pseudocódigo se ve así:

<!-- Component: <FeaturedDirector> -->
<div>
  Country: {country}
  {foreach films as film}
    <Film film={film} />
  {/foreach}
</div>
 
<!-- Component: <Film> -->
<div>
  Title: {title}
  Pic: {thumbnail}
  {foreach actors as actor}
    <Actor actor={actor} />
  {/foreach}
</div>
 
<!-- Component: <Actor> -->
<div>
  Name: {name}
  Photo: {avatar}
</div>

Luego identificamos qué datos se necesitan para cada componente.

Identificación de las propiedades de datos para cada componente

Y construimos nuestra consulta GraphQL para obtener los datos requeridos:

query {
  featuredDirector {
    name
    country
    avatar
    films {
      title
      thumbnail
      actors {
        name
        avatar
      }
    }
  }
}

Como se puede apreciar, existe una relación directa entre la forma de una jerarquía de componentes y una consulta GraphQL. De hecho, una consulta GraphQL puede incluso considerarse la representación de una jerarquía de componentes.

Resolución de la consulta de GraphQL mediante componentes del lado del servidor

Dado que una consulta graphQL tiene la misma forma que una jerarquía de componentes, PoP transforma la consulta en su jerarquía de componentes equivalente, la resuelve utilizando su enfoque para obtener datos para los componentes y, finalmente, recrea la forma de la consulta para enviar los datos en la respuesta.

Veamos cómo funciona esto.

Para procesar los datos, PoP convierte los tipos GraphQL en componentes: <FeaturedDirector>=>Director, <Film>=>Film, <Actor>=>Actor, y utilizando el orden en que aparecen en la consulta, PoP crea una jerarquía de componentes virtuales con los mismos elementos: componente raíz Director, que envuelve el componente Film, que envuelve componente Actor.

A partir de ahora, hablar de tipos de GraphQL o componentes PoP no hace ninguna diferencia.

Para cargar tus datos, PoP trata con ellos en «iteraciones», recuperando los datos de objeto para cada tipo en tu propia iteración, de la siguiente manera:

Tratar con tipos en iteraciones

El motor de carga de datos de PoP implementa el siguiente pseudo-algoritmo para cargar los datos:

Preparación:

  1. Hace que una cola vacía almacene la lista de los IDs de los objetos que deben obtenerse de la base de datos, organizados por tipo (cada entrada será: [type => list of IDs])
  2. Recupera el identificador del objeto director destacado y lo coloca en la cola en el texto Director

Bucle hasta que no haya más entradas en la cola:

  1. Obtiene la primera entrada de la cola: el tipo y la lista de IDs (por ejemplo: Director y [2]), y elimina esta entrada de la cola
  2. Ejecuta una única consulta en la base de datos para recuperar todos los objetos de ese tipo con esos certificados
  3. Si el tipo tiene campos relacionales (por ejemplo: el tipo Director tiene un campo relacional films de tipo Film), recopila todos los ID de estos campos de todos los objetos recuperados en la iteración actual (por ejemplo: todos los IDs en el campo films de todos los objetos de tipo Director), y coloca estos IDs en la cola bajo el tipo correspondiente (por ejemplo: IDs [3, 8] bajo tipo Film).

Al final de las iteraciones, haremos cargado todos los datos de objetos para todos los tipos, como éste:

Tratar con tipos en iteraciones

Observa cómo se recopilan todos los ID de un tipo, hasta que el tipo se procesa en la cola. Si, por ejemplo, añadimos un campo preferredActors relacional al tipo Director, estos ID se añadirían a la cola bajo tipo Actor, y se procesaría junto con los ID del campo actors del tipo Film:

Tratar con tipos en iteraciones

Sin embargo, si se has procesado un tipo y luego necesitamos cargar más datos de ese tipo, entonces es una nueva iteración en ese tipo. Por ejemplo, agregar un campo relacional preferredDirector al tipo Author, hará que el tipo Director se agregue a la cola una vez más:

Iteración sobre un tipo repetido

Presta atención también a que aquí podemos usar un mecanismo de almacenamiento en caché: en la segunda iteración para el tipo Director, el objeto con ID 2 no se recupera nuevamente, ya que ya se recuperó en la primera iteración por lo que se puede tomar de la caché.

Ahora que hemos obtenido todos los datos del objeto, debemos darle forma a la respuesta esperada, reflejando la consulta GraphQL. Tal como están actualmente, los datos se organizan como en una base de datos relacional:

Tabla para el tipo Director:

IDENTIFICACIÓN nombre país avatar películas
2 Jorge Lucas ESTADOS UNIDOS george-lucas.jpg [3, 8]

Tabla para el tipo de película:

IDENTIFICACIÓN título miniatura actores
3 La amenaza fantasma episodio-1.jpg [4, 6]
8 Ataque de los clones episodio-2.jpg [6, 7]

Tabla para el tipo Actor:

IDENTIFICACIÓN nombre avatar
4 Ewan McGregor mcgregor.jpg
6 Nathalie Portman portman.jpg
7 Hayden Christensen christensen.jpg

En esta etapa, PoP tiene todos los datos organizados como tablas, y cómo se relaciona cada tipo entre sí (es decir, Director referencia a Film a través del campo films, Film referencia a Actor a través del campo actors). Luego, iterando la jerarquía de componentes desde la raíz, navegando por las relaciones y recuperando los objetos correspondientes de las tablas relacionales, PoP producirá la forma de árbol a partir de la consulta GraphQL:

Respuesta en forma de árbol

Finalmente, la impresión de los datos en la salida produce la respuesta con la misma forma de la consulta GraphQL:

{
  data: {
    featuredDirector: {
      name: "George Lucas",
      country: "USA",
      avatar: "george-lucas.jpg",
      films: [
        {
          title: "Star Wars: Episode I",
          thumbnail: "episode-1.jpg",
          actors: [
            {
              name: "Ewan McGregor",
              avatar: "mcgregor.jpg",
            },
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            }
          ]
        },
        {
          title: "Star Wars: Episode II",
          thumbnail: "episode-2.jpg",
          actors: [
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            },
            {
              name: "Hayden Christensen",
              avatar: "christensen.jpg",
            }
          ]
        }
      ]
    }
  }
}

5. Análisis del rendimiento del uso de componentes para resolver una consulta de GraphQL

Analicemos la notación O grande del algoritmo de carga de datos para comprender cómo crece el número de consultas ejecutadas en la base de datos a medida que crece el número de entradas, para asegurarnos de que esta solución sea efectiva.

El motor de carga de datos de PoP carga los datos en iteraciones correspondientes a cada tipo. En el momento en que inicie una iteración, ya tendrá la lista de todos los IDs para todos los objetos a recuperar, por lo tanto, puede ejecutar 1 sola consulta para obtener todos los datos de los objetos correspondientes. A continuación, se deduce que el número de consultas a la base de datos crecerá linealmente con el número de tipos involucrados en la consulta. En otras palabras, la complejidad de tiempo es O(n), donde n es el número de tipos en la consulta (sin embargo, si un tipo se itera más de una vez, entonces debe agregarse más de una vez a n).

Esta solución es muy performante, ciertamente más que la complejidad exponencial esperada al tratar con gráficos, o la complejidad logarítmica esperada al tratar con árboles.

Conclusión

Un servidor GraphQL no necesita usar gráficos para representar datos. En este artículo exploramos la arquitectura descrita por PoP, e implementada por GraphQL por PoP, que se basa en componentes y carga datos en iteraciones según el tipo.

A través de este enfoque, el servidor puede resolver consultas de GraphQL con complejidad de tiempo lineal, que es un mejor resultado que la complejidad de tiempo exponencial o logarítmica que se espera del uso de gráficos o árboles.

¿Qué opinas?

Escrito por Wombat

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

Cómo personalizar tu tienda de comercio electrónico con ShopIsle

Cómo obtener feeds en WordPress usando el plugin WP RSS Aggregator