Ejecutar varias consultas concurrentemente
Se pueden combinar varias consultas y ejecutarlas como una sola operación, reutilizando su estado y sus datos.
Esto es distinto del query batching, en el que el servidor GraphQL también ejecuta varias consultas en una sola petición, pero esas consultas simplemente se ejecutan una tras otra, independientemente unas de otras.
Esta característica mejora el rendimiento. En lugar de ejecutar consultas de forma independiente en diferentes peticiones (de modo que primero ejecutamos una operación contra el servidor GraphQL, después esperamos su respuesta, y después usamos ese resultado para realizar otra operación), podemos ejecutarlas juntas, evitando así la latencia de las varias peticiones.
La ejecución de varias consultas (Multiple Query Execution) también nos permite organizar mejor nuestras consultas GraphQL, dividiéndolas en unidades lógicas que dependen unas de otras, y que se ejecutan condicionalmente en función del resultado de una operación anterior.
Cómo usar la ejecución de varias consultas
Supongamos que queremos buscar todos los posts que mencionan el nombre del usuario con sesión iniciada. Normalmente, necesitaríamos dos consultas para conseguirlo:
Primero obtenemos el name del usuario:
query GetLoggedInUserName {
me {
name
}
}...y después, habiendo ejecutado la primera consulta, podemos pasar el name del usuario obtenido como variable $search para realizar la búsqueda en una segunda consulta:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution simplifica este proceso, permitiéndonos obtener todos los datos y ejecutar toda la lógica necesaria en una sola petición:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution se logra con el uso de estas directivas especiales:
@depends(directiva de operación): hace que una operación (ya seaqueryomutation) indique qué otras operaciones deben ejecutarse antes@export(directiva de campo): exporta algún valor de un campo de una operación, para inyectarlo como entrada a algún campo en otra operación@deferredExport(directiva de campo): Similar a@exportpero para usar con Multi-Field Directives.
Adicionalmente, las directivas @include y @skip también están disponibles como directivas de operación (normalmente son solo directivas de campo), y se pueden usar para ejecutar condicionalmente una operación si cumple alguna condición.
El servidor GraphQL creará la lista de operaciones a cargar y ejecutar, recuperándolas de cada @depends(on: ...), y exportará los valores de cualquier campo que contenga @export como una variable dinámica (con el nombre definido bajo el argumento as) para ser entrada en cualquier operación posterior.
Combinando estas directivas, podemos dividir cualquier funcionalidad compleja en pasos intermedios, alternando operaciones query y mutation, añadir sus dependencias en el orden requerido, y ejecutarlas todas en una sola petición definiendo la operación más externa en ?operationName=... (en el ejemplo anterior, eso sería ?operationName=GetPostsContainingString).
Definiendo las operaciones a cargar y ejecutar mediante @depends
Cuando el documento GraphQL contiene varias operaciones, indicamos al servidor cuál ejecutar mediante el parámetro URL ?operationName=...; en caso contrario, se ejecutará la última operación.
Partiendo de esta operación inicial, el servidor recopilará todas las operaciones a ejecutar, que se definen añadiendo la directiva depends(on: [...]), y las ejecutará en el orden correspondiente respetando las dependencias.
El argumento operations de la directiva recibe un array de nombres de operación ([String]), o también podemos proporcionar un único nombre de operación (String).
En esta consulta, pasamos ?operationName=Four, y las operaciones ejecutadas (ya sean query o mutation) serán ["One", "Two", "Three", "Four"]:
mutation One {
# Do something ...
}
mutation Two {
# Do something ...
}
query Three @depends(on: ["One", "Two"]) {
# Do something ...
}
query Four @depends(on: "Three") {
# Do something ...
}Compartir datos entre consultas mediante @export
La directiva @export exporta el valor de un campo (o conjunto de campos) en una variable dinámica, para usarse como entrada en algún campo de otra consulta.
Por ejemplo, en esta consulta exportamos el nombre del usuario con sesión iniciada, y usamos este valor para buscar posts que contengan esta cadena (nota que la variable $loggedInUserName, al ser dinámica, no necesita definirse en la operación FindPosts):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}Salidas de variables dinámicas
@export puede producir 6 salidas diferentes, según una combinación de:
- El valor del argumento
type(ya seaSINGLE,LISToDICTIONARY) - Si la directiva se aplica a un único campo, o a varios campos (mediante el módulo Multi-Field Directives)
Las 6 salidas posibles entonces son:
- Tipo
SINGLE:- Campo único
- Multi-campo
- Tipo
LIST:- Campo único
- Multi-campo
- Tipo
DICTIONARY:- Campo único
- Multi-campo
Tipo SINGLE / Campo único
La salida es un único valor al pasar el parámetro type: SINGLE (que está establecido como valor por defecto).
En esta consulta:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...la variable dinámica $postTitle tendrá el valor:
"Hello world!"Nota que si SINGLE se aplica sobre un array de entidades, entonces el valor de la última entidad es el que se exporta.
En esta consulta:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...la variable dinámica $postTitle tendrá el valor del post con ID 5:
"Everything good?"Tipo SINGLE / Multi-campo
Si @export se aplica sobre varios campos (añadiendo el parámetro affectAdditionalFieldsUnderPos proporcionado por el módulo Multi-Field Directives), entonces el valor que se establece en la variable dinámica es un diccionario de { key: alias del campo, value: valor del campo } (de tipo JSONObject).
Esta consulta:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta la variable dinámica $postData con valor:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}Tipo LIST / Campo único
La variable dinámica contendrá un array con el valor del campo de todas las entidades consultadas (del campo que las envuelve), al pasar el parámetro type: LIST.
Al ejecutar esta consulta (en la que las entidades consultadas son posts con ID 1 y 5):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...la variable dinámica $postTitles tendrá el valor:
[
"Hello world!",
"Everything good?"
]Tipo LIST / Multi-campo
Obtenemos un array de diccionarios (de tipo JSONObject), cada uno conteniendo los valores de los campos sobre los que se aplica la directiva.
Esta consulta:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta la variable dinámica $postsData con valor:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]Tipo DICTIONARY / Campo único
La variable dinámica contendrá un diccionario (de tipo JSONObject) con el ID de la entidad consultada como clave, y el valor del campo como valor, al pasar el parámetro type: DICTIONARY.
Esta consulta:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...exporta la variable dinámica $postIDTitles con valor:
{
"1": "Hello world!",
"5": "Everything good?"
}Tipo DICTIONARY / Multi-campo
En esta combinación, exportamos un diccionario de diccionarios: { key: ID de la entidad, value: { key: alias del campo, value: valor del campo } } (usando un tipo JSONObject que contendrá entradas de tipo JSONObject).
Esta consulta:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta la variable dinámica $postsIDProperties con valor:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Ejecución condicional de operaciones
Cuando Multiple Query Execution está activado, las directivas @include y @skip también están disponibles como directivas de operación, y se pueden usar para ejecutar condicionalmente una operación si cumple alguna condición.
Por ejemplo, en esta consulta, la operación CheckIfPostExists exporta una variable dinámica $postExists y, solo si su valor es true, se ejecutará la mutación ExecuteOnlyIfPostExists:
query CheckIfPostExists($id: ID!) {
# Initialize the dynamic variable to `false`
postExists: _echo(value: false) @export(as: "postExists")
post(by: { id: $id }) {
# Found the Post => Set dynamic variable to `true`
postExists: _echo(value: true) @export(as: "postExists")
}
}
mutation ExecuteOnlyIfPostExists
@depends(on: "CheckIfPostExists")
@include(if: $postExists)
{
# Do something...
}Exportar valores al iterar un array u objeto JSON
@export respeta la cardinalidad de cualquier meta-directiva que la englobe.
En particular, siempre que @export esté anidada bajo una meta-directiva que itera sobre los elementos de un array o las propiedades de un objeto JSON (es decir, @underEachArrayItem y @underEachJSONObjectProperty), entonces el valor exportado será un array.
Esta consulta:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produce $contentAttributes con valor:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]En contraste, la misma consulta que accede a un elemento específico del array en lugar de iterar sobre todos ellos (reemplazando @underEachArrayItem con @underArrayItem(index: 0)) exportará un único valor.
Esta consulta:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produce $contentAttributes con valor:
"List Block"Orden de ejecución de directivas
Si hay otras directivas antes de @export, el valor exportado reflejará las modificaciones de esas directivas previas.
Por ejemplo, en esta consulta, según @export se realice antes o después de @strUpperCase, el resultado será diferente:
query One {
id
# First export "root", only then will be converted to "ROOT"
@export(as: "id")
@strUpperCase
again: id
# First convert to "ROOT" and then export this value
@strUpperCase
@export(as: "again")
}
query Two @depends(on: "One") {
mirrorID: _echo(value: $id)
mirrorAgain: _echo(value: $again)
}Produciendo:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Multi-Field Directives
Cuando la característica Multi-Field Directives está activada y exportamos el valor de varios campos en un diccionario, usa @deferredExport en lugar de @export para garantizar que todas las directivas de todos los campos involucrados se hayan ejecutado antes de exportar el valor del campo.
Por ejemplo, en esta consulta, el primer campo tiene aplicada la directiva @strUpperCase, y el segundo tiene @titleCase. Al ejecutar @deferredExport, el valor exportado tendrá estas directivas aplicadas:
query One {
id @strUpperCase # Will be exported as "ROOT"
again: id @titleCase # Will be exported as "Root"
@deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}
query Two @depends(on: "One") {
mirrorProps: _echo(value: $props)
}Produciendo:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}Spec GraphQL
Esta funcionalidad no forma parte actualmente de la spec GraphQL, pero se ha solicitado: