Diseño de mutaciones GraphQL: mutaciones anémicas

Las mutaciones son una de las partes más complicadas de un esquema GraphQL para diseñar. Pasamos mucho tiempo hablando sobre consultas GraphQL y lo fácil que son de usar. Sin embargo, las mutaciones obtienen mucho menos amor.

Pasé los últimos días pensando en cómo pienso sobre el diseño de mutación y cómo se relaciona con RPC, REST y el diseño impulsado por dominio. Publicaré una serie de publicaciones sobre algunos temas relacionados con GraphQL Mutation Design.

En esta publicación, nos centraremos en lo que he estado llamando mutaciones anémicas.

Mutaciones anémicas ™ ️

Existe esta cosa llamada Modelos de dominio anémico en el mundo del diseño impulsado por dominio. Explicado rápidamente, el AnemicDomainModel es un patrón en el que nuestro modelo de dominio contiene solo datos, sin ninguno de los comportamientos asociados con él.

Creo que podemos hacer un vínculo bastante interesante entre este "Anti-Pattern" y el diseño de un buen sistema de mutación en nuestras API GraphQL. Aquí hay un ejemplo de lo que describiría como una mutación anémica. Imagine un objeto Checkout, parte de algún esquema GraphQL de comercio electrónico:

Lo primero que podemos ver es cómo todos los campos de entrada en UpdateCheckoutInput son opcionales. Como han optado por una mutación CRUD simple, y dado que pueden querer permitir actualizaciones parciales durante el proceso de pago, tiene sentido al principio hacer que todo sea opcional. Hay algunas cosas sobre este diseño que realmente no me gustan.

Primero, dado que decidimos usar una mutación de "grano grueso" que nos permite realizar cambios en este objeto Checkout, tuvimos que hacer que todo fuera anulable (opcional). Una de las grandes fortalezas de GraphQL es su sistema de tipos. Al eliminar cualquier restricción de nulabilidad en los campos de entrada, hemos diferido prácticamente toda esta validación al tiempo de ejecución, en lugar de utilizar el esquema para guiar al usuario de la API hacia el uso adecuado.

El segundo punto es lo que hace que esta mutación sea una mutación anémica. Hemos diseñado el tipo de entrada de una manera muy centrada en los datos, en lugar de centrarnos en los comportamientos. Imagine, por ejemplo, que nuestro usuario de la API quiere usar la API para crear un botón "Agregar al carrito":

  • Debido a que la mutación se enfoca tanto en los datos y no en los comportamientos, nuestros clientes deben adivinar cómo realizar una acción específica. ¿Qué sucede si agregar un artículo a nuestro proceso de pago realmente requiere actualizaciones de algunos otros atributos? Nuestro cliente solo aprendería que a través de errores en tiempo de ejecución, o peor, puede terminar en un estado incorrecto al olvidar actualizar un atributo.
  • Hemos agregado una sobrecarga cognitiva a los clientes porque necesitan seleccionar el conjunto de campos para actualizar cuando desean realizar una determinada acción, como "Agregar al carrito".
  • Debido a que nos enfocamos en la forma de los datos internos de un Pago, y no en los comportamientos potenciales de un Pago, no indicamos explícitamente que incluso sea posible realizar estas acciones, les permitimos adivinar mirando nuestro modelo de datos.

Veamos cómo podríamos diseñar esta mutación para que explícitamente nos diga cómo agregar un elemento a un pago:

Hemos solucionado la mayoría de los problemas de los que hablamos anteriormente:

  • Nuestro esquema está fuertemente tipado. Nada es opcional en esta mutación, nuestros clientes saben exactamente qué proporcionar para agregar un artículo a un pago.
  • No más adivinanzas. En lugar de encontrar qué datos actualizar, agregamos un elemento. A nuestros clientes no les importa qué datos deben actualizarse en estos casos, solo quieren agregar un elemento.
  • El conjunto de posibles errores que pueden ocurrir durante la ejecución de esta mutación se ha reducido considerablemente. Nuestro solucionador puede devolver errores más finos.
  • No hay forma de que el cliente entre en un estado extraño usando esta mutación porque eso es manejado por nuestro resolutor.

Un efecto secundario interesante de diseñar mutaciones de esta manera es que desarrollar estas mutaciones se vuelve mucho más directo en el backend. Dado que la entrada de mutación es mucho más predecible, podemos eliminar muchas validaciones no necesarias en el resolutor. Otra cosa realmente genial es que la emisión de eventos también se vuelve más fácil. Imagine que intenta emitir un evento ItemAdded cada vez que un usuario agrega un elemento a su pago. En una gran mutación gorda con campos de entrada opcionales, necesitamos verificar cada escenario con condicionales, y emitir eventos dependiendo de estas condiciones, se vuelve desordenado.

En cierto modo, encuentro estos lazos en muchos con algunos puntos que Caleb Meredith hizo en su publicación (https://dev-blog.apollodata.com/designing-graphql-mutations-e09de826ed97) hace un tiempo, que realmente disfruté .

En los próximos días y semanas, publicaré algunas otras publicaciones sobre diseño de mutación GraphQL. Escribiré sobre la administración del estado con mutaciones, el diseño para consultas estáticas y el buen diseño de argumentos.

Gracias por leer . Si te ha gustado esta publicación, ¡puedes seguirme en Twitter! Probablemente también disfrutarías de The Little Book of GraphQL Schema Design en el que estoy trabajando duro para terminar. ¡Significaría mucho si se suscribiera para recibir actualizaciones!