Representando la filosofía de WordPress en GraphQL
WordPress es un CMS codificado en PHP. Pero, aunque PHP es la base, WordPress también tiene una filosofía en la que se priorizan las necesidades del usuario sobre la conveniencia del desarrollador. Esa filosofía establece un contrato implícito entre los desarrolladores que crean temas y complementos de WordPress y el usuario que administra un sitio de WordPress.
GraphQL es una interfaz que recupera datos del servidor y puede enviarlos. Un servidor GraphQL puede tener su propia obstinación en la forma en que implementa la especificación GraphQL, como para priorizar un determinado comportamiento sobre otro.
¿Puede la filosofía de WordPress que depende de la arquitectura del lado del servidor coexistir con un lenguaje de consulta basado en JavaScript que pasa datos a través de una API?
Analizamos esa pregunta y explicamos cómo el complemento de WordPress GraphQL API que escribí establece un puente entre las dos arquitecturas.
Es posible que conozcas WPGraphQL. El complemento GraphQL API para WordPress (o “GraphQL API” de ahora en adelante) es un servidor GraphQL diferente para WordPress, con características diferentes.
Conciliando la filosofía de WordPress dentro del servicio GraphQL
Esta tabla contiene el comportamiento esperado de una aplicación o complemento de WordPress y cómo puede ser interpretado por un servicio GraphQL que se ejecuta en WordPress:
categoría | Comportamiento esperado de la aplicación WordPress | Interpretación del servicio GraphQL que se ejecuta en WordPress |
---|---|---|
Accediendo a datos | democratizar la publicación: cualquier usuario (independientemente de que tenga habilidades técnicas o no) debe poder utilizar el software. | democratizar el acceso y la publicación de datos: cualquier usuario (independientemente de que tenga habilidades técnicas o no) debe poder visualizar y modificar el esquema GraphQL y ejecutar una consulta GraphQL. |
extensibilidad | La aplicación debe ser extensible mediante complementos. | El esquema GraphQL debe ser extensible a través de complementos. |
Comportamiento dinámico | El comportamiento de la aplicación se puede modificar mediante ganchos. | Los resultados de la resolución de una consulta se pueden modificar mediante directivas. |
Localización | La aplicación debe estar traducida para ser utilizada por personas de cualquier región y que hablen cualquier idioma. | El esquema GraphQL debe estar localizado para que lo utilicen personas de cualquier región y que hablen cualquier idioma. |
Interfaces de usuario | La instalación y operación de la funcionalidad debe realizarse a través de una interfaz de usuario, recurriendo lo menos posible al código. | Agregar nuevas entidades (tipos, campos, directivas) al esquema GraphQL, configurarlas, ejecutar consultas y permisos definidos para acceder al servicio debe realizarse a través de una interfaz de usuario, recurriendo lo menos posible al código. |
Control de acceso | El acceso a las funcionalidades se puede otorgar a través de roles y permisos de usuario. | El acceso al esquema GraphQL se puede otorgar a través de roles y permisos de usuario. |
Prevenir conflictos | Los desarrolladores no saben de antemano quién usará sus complementos o qué configuración/entorno ejecutarán esos sitios, lo que significa que el complemento debe estar preparado para conflictos (como tener dos complementos que definen el servicio SMTP) e intentar evitarlos, en la medida de lo posible. como sea posible | Los desarrolladores no saben de antemano quién accederá y modificará el esquema GraphQL, o qué configuración/entorno ejecutarán esos sitios, lo que significa que el complemento debe estar preparado para conflictos (como tener dos complementos con el mismo nombre para un tipo en el esquema GraphQL). ). ), e intentar prevenirlos, en la medida de lo posible. |
Veamos cómo la API GraphQL lleva a cabo estas ideas.
Accediendo a datos
Al igual que REST, un servicio GraphQL debe codificarse mediante funciones PHP. ¿Quién hará esto y cómo?
Modificar el esquema GraphQL a través del código
El esquema GraphQL incluye tipos, campos y directivas. Estos se tratan mediante solucionadores, que son piezas de código PHP. ¿Quién debería crear estos solucionadores?
La mejor estrategia es que la API GraphQL ya satisface el esquema básico de GraphQL con todas las entidades conocidas en WordPress (incluidas publicaciones, usuarios, comentarios, categorías y etiquetas) y simplifica la introducción de nuevos solucionadores, por ejemplo, para tipos de publicaciones personalizadas. . (CPT).
Así es como el complemento ya proporciona la entidad de usuario. El User
tipo se proporciona a través de este código:
class UserTypeResolver extends AbstractTypeResolver{ public function getTypeName(): string { return 'User'; } public function getSchemaTypeDescription(): ?string { return __('Representation of a user', 'users'); } public function getID(object $user) { return $user-ID; } public function getTypeDataLoaderClass(): string { return UserTypeDataLoader::class; }}
El solucionador de tipos no carga directamente los objetos desde la base de datos, sino que delega esta tarea a un TypeDataLoader
objeto (en el ejemplo anterior, de UserTypeDataLoader
). Este desacoplamiento debe seguir los principios SOLID, proporcionando diferentes entidades para abordar diferentes responsabilidades, a fin de hacer que el código sea mantenible, extensible y comprensible.
Agregando username
campos email
y url
al User
tipo se realiza a través de un FieldResolver
objeto:
class UserFieldResolver extends AbstractDBDataFieldResolver{ public static function getClassesToAttachTo(): array { return [ UserTypeResolver::class, ]; } public static function getFieldNamesToResolve(): array { return [ 'username', 'email', 'url', ]; } public function getSchemaFieldDescription( TypeResolverInterface $typeResolver, string $fieldName ): ?string { $descriptions = [ 'username' = __("User's username handle", "graphql-api"), 'email' = __("User's email", "graphql-api"), 'url' = __("URL of the user's profile in the website", "graphql-api"), ]; return $descriptions[$fieldName]; } public function getSchemaFieldType( TypeResolverInterface $typeResolver, string $fieldName ): ?string { $types = [ 'username' = SchemaDefinition::TYPE_STRING, 'email' = SchemaDefinition::TYPE_EMAIL, 'url' = SchemaDefinition::TYPE_URL, ]; return $types[$fieldName]; } public function resolveValue( TypeResolverInterface $typeResolver, object $user, string $fieldName, array $fieldArgs = [] ) { switch ($fieldName) { case 'username': return $user-user_login; case 'email': return $user-user_email; case 'url': return get_author_posts_url($user-ID); } return null; }}
Como se puede observar, la definición de un campo para el esquema GraphQL, y su resolución, se ha dividido en multitud de funciones:
getSchemaFieldDescription
getSchemaFieldType
resolveValue
Otras funciones incluyen:
getSchemaFieldArgs
: para declarar los argumentos del campo (incluido su nombre, descripción, tipo y si son obligatorios o no)isSchemaFieldResponseNonNullable
: para indicar si un campo no admite valores NULLgetImplementedInterfaceClasses
: para definir los solucionadores para las interfaces implementadas por los camposresolveFieldTypeResolverClass
: para definir el tipo de resolución cuando el campo es una conexiónresolveFieldMutationResolverClass
: para definir el solucionador cuando el campo ejecuta mutaciones
Este código es más legible que si toda la funcionalidad se satisface mediante una sola función o mediante una matriz de configuración, lo que facilita la implementación y el mantenimiento de los solucionadores.
Recuperar complementos o datos CPT personalizados
¿Qué sucede cuando un complemento no ha integrado sus datos en el esquema GraphQL mediante la creación de nuevos solucionadores de tipos y campos? ¿Podría el usuario consultar datos de este complemento a través de GraphQL?
Por ejemplo, digamos que WooCommerce tiene un CPT para productos, pero no presentamos el Product
tipo correspondiente al esquema GraphQL. ¿Es posible recuperar los datos del producto?
En cuanto a las entidades CPT, sus datos se pueden obtener mediante tipo GenericCustomPost
, que actúa como una especie de comodín, para abarcar cualquier tipo de publicación personalizada instalada en el sitio. Los registros se recuperan mediante consulta Root.genericCustomPosts(customPostTypes: [cpt1, cpt2, ...])
(en esta notación para campos, Root
es el tipo y genericCustomPosts
es el campo).
Luego, para recuperar los datos del producto, correspondientes al CPT con nombre "wc_product"
, ejecutamos esta consulta:
{ genericCustomPosts(customPostTypes: "[wc_product]") { id title url date }}
Sin embargo, todos los campos disponibles son solo aquellos presentes en cada entidad CPT: title
, url
, date
, etc. Si el CPT de un producto tiene datos de precio, el campo correspondiente price
no está disponible. wc_product
se refiere a un CPT creado por el complemento WooCommerce, por lo que, para eso, WooCommerce o los desarrolladores del sitio web tendrán que implementar el Product
tipo y definir sus propios campos personalizados.
Los CPT se utilizan a menudo para gestionar datos privados, que no deben exponerse a través de la API. Por esta razón, la API GraphQL inicialmente solo expone el Page
tipo y requiere definir qué otros CPT pueden tener sus datos consultados públicamente:
Transición de REST a GraphQL mediante consultas persistentes
Si bien GraphQL se proporciona como complemento, WordPress tiene soporte integrado para REST, a través de la API REST de WP. En algunas circunstancias, los desarrolladores que trabajan con la API REST de WP pueden tener problemas para realizar la transición a GraphQL.
Por ejemplo, considere estas diferencias:
- Un punto final REST tiene su propia URL y se puede consultar a través de
GET
, mientras que GraphQL normalmente opera a través de un único punto final, consultadoPOST
solo a través de - El punto final REST se puede almacenar en caché en el lado del servidor (cuando se consulta a través de
GET
), mientras que el punto final GraphQL normalmente no puede
Como consecuencia, REST proporciona un mejor soporte listo para usar para el almacenamiento en caché, lo que hace que la aplicación tenga más rendimiento y reduzca la carga en el servidor. GraphQL, en cambio, pone más énfasis en el almacenamiento en caché en el lado del cliente, como lo admite el cliente Apollo.
Después de cambiar de REST a GraphQL, ¿el desarrollador necesitará rediseñar la aplicación en el lado del cliente, introduciendo el cliente Apollo solo para introducir una capa de almacenamiento en caché? Sería lamentable.
La función “consultas persistentes” proporciona una solución para esta situación. Las consultas persistentes combinan REST y GraphQL, lo que nos permite:
- crear consultas usando GraphQL, y
- publicar las consultas en su propia URL, similar a los puntos finales REST.
El punto final de consulta persistente tiene el mismo comportamiento que un punto final REST: se puede acceder a él a través de GET
y se puede almacenar en caché en el lado del servidor. Pero se creó utilizando la sintaxis GraphQL y los datos expuestos no tienen ninguna recuperación excesiva o insuficiente.
extensibilidad
La arquitectura de la API GraphQL definirá lo fácil que es añadir nuestras propias extensiones.
Tipo de desacoplamiento y solucionadores de campo
La API GraphQL utiliza el patrón Publicar-suscribir para que los campos se “suscriban” a los tipos.
Reevaluando el solucionador de campo de antes:
class UserFieldResolver extends AbstractDBDataFieldResolver{ public static function getClassesToAttachTo(): array { return [UserTypeResolver::class]; } public static function getFieldNamesToResolve(): array { return [ 'username', 'email', 'url', ]; }}
El User
tipo no sabe de antemano qué campos satisfarán, pero estos ( username
, email
y url
) son inyectados en el tipo por el solucionador de campos.
De esta manera, el esquema GraphQL se vuelve fácilmente extensible. Simplemente agregando un solucionador de campos, cualquier complemento puede agregar nuevos campos a un tipo existente (como WooCommerce agregando un campo para User.shippingAddress
), o anular cómo se resuelve un campo (como redefinir User.url
para devolver el sitio web del usuario).
Enfoque de código primero
Los complementos deben poder ampliar el esquema GraphQL. Por ejemplo, podría poner a disposición un nuevo Product
tipo, agregar un coauthors
campo adicional al Post
tipo, proporcionar una @sendEmail
directiva o cualquier otra cosa.
Para lograr esto, la API GraphQL sigue un enfoque de código primero , en el que el esquema se genera a partir de código PHP, en tiempo de ejecución.
El enfoque alternativo, llamado SDL-first (lenguaje de definición de esquemas), requiere que el esquema se proporcione por adelantado, por ejemplo, a través de algún .gql
archivo.
La principal diferencia entre estos dos enfoques es que, en el enfoque de código primero, el esquema GraphQL es dinámico y adaptable a diferentes usuarios o aplicaciones. Esto se adapta a WordPress, donde un solo sitio podría impulsar varias aplicaciones (como un sitio web y una aplicación móvil) y personalizarse para diferentes clientes. La API GraphQL hace explícito este comportamiento a través de la función “puntos finales personalizados”, que permite crear diferentes puntos finales, con acceso a diferentes esquemas GraphQL, para diferentes usuarios o aplicaciones.
Para evitar problemas de rendimiento, el esquema se vuelve estático almacenándolo en caché en el disco o la memoria, y se vuelve a generar cada vez que se instala un nuevo complemento que amplía el esquema o cuando el administrador actualiza la configuración.
Soporte para funciones novedosas
Otro beneficio de utilizar el enfoque de código primero es que nos permite proporcionar funciones completamente nuevas que se pueden optar, antes de que sean compatibles con la especificación GraphQL.
Por ejemplo, se solicitaron mutaciones anidadas para la específica pero aún no se aprobaron. La API GraphQL cumple con la específica, utilizando tipos QueryRoot
y MutationRoot
para manejar consultas y mutaciones respectivamente, como se exponen en el esquema estándar. Sin embargo, al habilitar la función de “mutaciones anidadas”, el esquema se transforma y tanto las consultas como las mutaciones serán manejadas por un solo Root
tipo, brindando soporte para mutaciones anidadas.
Veamos esta novedosa característica en acción. En esta consulta, primero consultamos la publicación Root.post
, luego ejecutamos una mutación Post.addComment
en ella y obtenemos el objeto de comentario creado, y finalmente ejecutamos una mutación Comment.reply
en ella y consultamos algunos de sus datos (descomentar la primera mutación para iniciar sesión como usuario, para que esté permitido). para agregar comentarios):
# mutation {# loginUser(# usernameOrEmail:"test",# password:"pass"# ) {# id# name# }# }mutation { post(id:1459) { id title addComment(comment:"That's really beautiful!") { id date content author { id name } reply(comment:"Yes, it is!") { id date content } } }}
Comportamiento dinámico
WordPress utiliza ganchos (filtros y acciones) para modificar el comportamiento. Los ganchos son piezas simples de código que pueden anular un valor o permitir ejecutar una acción personalizada, siempre que se activen.
¿Existe un equivalente en GraphQL?
Directivas para anular la funcionalidad
Al buscar un mecanismo similar para GraphQL, llegué a la conclusión de que las directivas podrían considerarse equivalentes a los ganchos de WordPress hasta cierto punto: al igual que un gancho de filtro, una directiva es una función que modifica el valor de un campo, aumentando así algunas otras funcionalidades.
Por ejemplo, digamos que recuperamos una lista de títulos de publicaciones con esta consulta:
query { posts { title }}
…lo que produce esta respuesta:
{ "data": { "posts": [ { "title": "Scheduled by Leo" }, { "title": "COPE with WordPress: Post demo containing plenty of blocks" }, { "title": "A lovely tango, not with leo" }, { "title": "Hello world!" }, ] }}
Estos resultados están en inglés. ¿Cómo podemos traducirlos al español? Con una directiva @translate
aplicada en el campo title
(implementada a través de esta directiva resolver), que obtiene el valor del campo como entrada, llama a la API del Traductor de Google para traducirlo y su resultado anula la entrada original, como en esta consulta:
query { posts { title @translate(from:"en", to"es") }}
…lo que produce esta respuesta:
{ "data": { "posts": [ { "title": "Programado por Leo" }, { "title": "COPE con WordPress: publica una demostración que contiene muchos bloques" }, { "title": "Un tango lindo, no con leo" }, { "title": "¡Hola Mundo!" } ] }}
Tenga en cuenta que a las directivas no les preocupa quién es la entrada. En este caso, era un Post.title
campo, pero podría haber sido Post.excerpt
, Comment.content
o cualquier otro campo de tipo String
. Luego, la resolución de campos y la anulación de su valor se desacoplan limpiamente y las directivas siempre son reutilizables.
Directivas para conectarse a terceros
A medida que WordPress sigue convirtiéndose en el sistema operativo de la web (actualmente impulsa el 39% de todos los sitios, más que cualquier otro software), también aumenta progresivamente sus interacciones con servicios externos (piense en Stripe para pagos, Slack para notificaciones, AWS S3 para hosting). bienes, entre otros).
Como hemos visto anteriormente, las directivas se pueden usar para anular la respuesta de un campo. ¿Pero de dónde viene el nuevo valor? Podría provenir de alguna función local, pero perfectamente también podría provenir de algún servicio externo (como la directiva que @translate
vimos anteriormente, que recupera el nuevo valor de la API del Traductor de Google).
Por esta razón, GraphQL API ha decidido facilitar la comunicación de las directivas con API externas, permitiendo que esos servicios transformen los datos del sitio de WordPress al ejecutar una consulta, como por ejemplo para:
- traducción,
- compresión de imágenes,
- abastecimiento a través de una CDN, y
- envío de correos electrónicos, SMS y notificaciones de Slack.
De hecho, GraphQL API ha decidido hacer que las directivas sean lo más poderosas posible, convirtiéndolas en componentes de bajo nivel en la arquitectura del servidor, incluso haciendo que la resolución de la consulta se base en una canalización de directivas. Esto otorga a las directivas la facultad de realizar autorizaciones, validaciones y modificaciones de la respuesta, entre otras.
Localización
A los servidores GraphQL que utilizan el enfoque SDL-first les resulta difícil localizar la información en el esquema (el problema correspondiente a la especificación se creó hace más de cuatro años y aún no tiene solución).
Sin embargo, utilizando el enfoque de código primero, la API GraphQL puede localizar las descripciones de una manera sencilla, a través de la __('some text', 'domain')
función PHP, y las cadenas localizadas se recuperarán de un archivo POT correspondiente a la región y el idioma seleccionados en el administrador de WordPress.
Por ejemplo, como vimos anteriormente, este código localiza las descripciones de los campos:
class UserFieldResolver extends AbstractDBDataFieldResolver{ public function getSchemaFieldDescription( TypeResolverInterface $typeResolver, string $fieldName ): ?string { $descriptions = [ 'username' = __("User's username handle", "graphql-api"), 'email' = __("User's email", "graphql-api"), 'url' = __("URL of the user's profile in the website", "graphql-api"), ]; return $descriptions[$fieldName]; }}
Interfaces de usuario
El ecosistema GraphQL está lleno de herramientas de código abierto para interactuar con el servicio, muchas de las cuales brindan la misma experiencia fácil de usar que se espera en WordPress.
La visualización del esquema GraphQL se realiza con GraphQL Voyager:
Esto puede resultar especialmente útil a la hora de crear nuestros propios CPT y comprobar cómo y desde dónde se puede acceder a ellos y qué datos están expuestos para ellos:
La ejecución de la consulta en el punto final GraphQL se realiza con GraphiQL:
Sin embargo, esta herramienta no es lo suficientemente sencilla para todos, ya que el usuario debe tener conocimientos de la sintaxis de consulta GraphQL. Entonces, además, se instala GraphiQL Explorer encima, para componer la consulta GraphQL haciendo clic en los campos:
Control de acceso
WordPress proporciona diferentes roles de usuario (administrador, editor, autor, colaborador y suscriptor) para administrar los permisos de los usuarios, y los usuarios pueden iniciar sesión wp-admin
(por ejemplo, el personal), iniciar sesión en el sitio público (por ejemplo, clientes), o no haber iniciado sesión o tener una cuenta (cualquier visitante). La API GraphQL debe tener en cuenta estos, permitiendo otorgar acceso granular a diferentes usuarios.
Otorgar acceso a las herramientas
La API GraphQL permite configurar quién tiene acceso a los clientes GraphiQL y Voyager para visualizar el esquema y ejecutar consultas en él:
- ¿Solo el administrador?
- ¿El personal?
- ¿Los clientes?
- ¿Abiertamente accesible para todos?
Por razones de seguridad, el complemento, de forma predeterminada, solo proporciona acceso al administrador y no expone abiertamente el servicio en Internet.
En las imágenes de la sección anterior, los clientes GraphiQL y Voyager están disponibles en wp-admin
, disponible solo para el usuario administrador. El usuario administrador puede otorgar acceso a usuarios con otros roles (editor, autor, colaborador) a través de la configuración:
En cuanto a otorgar acceso a nuestros clientes, o a cualquier persona en Internet abierta, no queremos darles acceso al administrador de WordPress. Luego, la configuración permite exponer las herramientas en una nueva URL pública (como mywebsite.com/graphiql
y mywebsite.com/graphql-interactive
). Exponer estas URL públicas es una opción voluntaria, establecida explícitamente por el administrador.
Conceder acceso al esquema GraphQL
La API REST de WP no facilita la personalización de quién tiene acceso a algún punto final o campo dentro de un punto final, ya que no se proporciona una interfaz de usuario y debe lograrse mediante código.
La API GraphQL, en cambio, utiliza los metadatos que ya están disponibles en el esquema GraphQL para permitir la configuración del servicio a través de una interfaz de usuario (impulsada por el editor de WordPress). Como resultado, los usuarios no técnicos también pueden administrar sus API sin tocar una línea de código.
La gestión del control de acceso a los diferentes campos (y directivas) del esquema se logra haciendo clic en ellos y seleccionando, en un menú desplegable, qué usuarios (como aquellos que han iniciado sesión o con capacidades específicas) pueden acceder a ellos.
Prevenir conflictos
El espacio de nombres ayuda a evitar conflictos cuando dos complementos usan el mismo nombre para sus tipos. Por ejemplo, si tanto WooCommerce como Easy Digital Downloads implementan un tipo llamado Product
, resultaría ambiguo ejecutar una consulta para recuperar productos. Luego, el espacio de nombres transformaría los nombres de los tipos en WooCommerceProduct
y EDDProduct
, resolviendo el conflicto.
Sin embargo, la probabilidad de que surja un conflicto de este tipo no es muy alta. Entonces, la mejor estrategia es deshabilitarlo de forma predeterminada (para mantener el esquema lo más simple posible) y habilitarlo solo si es necesario.
Si está habilitado, el servidor GraphQL asigna automáticamente los tipos de espacios de nombres utilizando el nombre del paquete PHP correspondiente (para el cual todos los paquetes siguen la Recomendación estándar de PHP PSR-4
). Por ejemplo, para este esquema GraphQL normal:
…con el espacio de nombres habilitado, Post
se convierte en PoPSchema_Posts_Post
, Comment
se convierte en PoPSchema_Comments_Comment
, etc.
Eso es todo amigos
Tanto WordPress como GraphQL son temas cautivadores por sí solos, por lo que la integración de WordPress y GraphQL me resulta muy atractiva. Habiendo estado en esto durante algunos años, puedo decir que diseñar la forma óptima para que un CMS antiguo administre el contenido y una nueva interfaz acceda a él es un desafío que vale la pena afrontar.
Podría seguir describiendo cómo la filosofía de WordPress puede influir en la implementación de un servicio GraphQL que se ejecuta en WordPress, hablando de ello incluso durante varias horas, utilizando mucho material que no está incluido en este artículo. Pero necesito parar... Así que pararé ahora.
Espero que este artículo haya logrado proporcionar una buena descripción general de los porqués y cómo satisfacer la filosofía de WordPress en GraphQL, como lo hace el complemento GraphQL API para WordPress.
Deja un comentario