Conceptos, ideas, estrategias
Conceptos, ideas, estrategiasEstrategias para el versionado de campos y directivas

Estrategias para el versionado de campos y directivas

Por favor, lee primero la guía Evolucionar el esquema mediante el versionado de campos, que explica la característica de "field versioning" en Gato GraphQL.

Gato GraphQL permite que los campos y directivas reciban el argumento versionConstraint, para elegir qué versión específica (es decir, implementación) del campo/directiva utilizar:

query GetPosts {
  posts(versionConstraint: "^1.0") {
    id
    title(versionConstraint: ">=2.1")
    excerpt @strUpperCase(versionConstraint: "~1.5.3")
  }
}

¿Qué debería ocurrir cuando no especificamos el argumento versionConstraint? Por ejemplo, ¿a qué versión debería resolver el campo surname en la consulta de abajo?

query GetSurname {
  account(id: 1) {
    # ¿Qué versión debería usarse? 1.0.0? 2.0.0?
    surname
  }
}

Tenemos dos preocupaciones aquí:

  1. Decidir cuál es la versión por defecto a usar cuando no se proporciona ninguna
  2. Informar al cliente de que hay varias versiones entre las que elegir

Antes de abordar estas preocupaciones, necesitamos averiguar lo bien que GraphQL proporciona feedback contextual al ejecutar una consulta.

Proporcionar feedback contextual al ejecutar consultas

Necesitamos señalar una circunstancia menos que ideal con GraphQL ahora mismo: no ofrece buena información contextual al ejecutar consultas. Esto es evidente con respecto a las deprecaciones, donde los datos de deprecación se muestran solo mediante introspección consultando los campos isDeprecated y deprecationReason en los tipos Field y Enum:

{
  __type(name: "Account") {
    name
    fields {
      name
      isDeprecated
      deprecationReason
    }
  }
}

La respuesta será:

{
  "data": {
    "__type": {
      "name": "Account",
      "fields": [
        {
          "name": "id",
          "isDeprecated": false,
          "deprecationReason": null
        },
        {
          "name": "name",
          "isDeprecated": false,
          "deprecationReason": null
        },
        {
          "name": "surname",
          "isDeprecated": true,
          "deprecationReason": "Use `personSurname`"
        },
        {
          "name": "personSurname",
          "isDeprecated": false,
          "deprecationReason": null
        }
      ]
    }
  }
}

Sin embargo, al ejecutar una consulta que involucra un campo deprecado…

query GetSurname {
  account(id: 1) {
    surname
  }
}

...la información de deprecación no aparecerá en la respuesta:

{
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

Esto significa que el desarrollador que ejecuta la consulta debe ejecutar activamente consultas de introspección para averiguar si el esquema fue actualizado y algún campo deprecado. Eso puede ocurrir… ¿una vez de vez en cuando? ¿Quizás nunca?

Sería una gran mejora hacia la revisión de consultas obsoletas si la API GraphQL proporcionara información de deprecación al ejecutar consultas que involucran campos deprecados. Esta información podría idealmente darse bajo una nueva entrada top-level deprecations, apareciendo después de errors y antes de data (siguiendo la sugerencia de la spec para el formato de respuesta).

Como una entrada top-level deprecations no forma parte de la spec, la característica "Proactive Feedback" de Gato GraphQL añade soporte para un mejor feedback en la respuesta a la consulta utilizando la entrada top-level comodín extensions, que permite extender el protocolo según sea necesario:

Información de deprecación en la respuesta de la consulta

Publicitar versiones mediante warnings

Acabamos de aprender que el servidor GraphQL puede usar la entrada top-level extensions para proporcionar deprecaciones. Podemos usar esta misma metodología para añadir una entrada warnings, en la que informamos al desarrollador de que un campo ha sido versionado. No proporcionamos esta información siempre; solo cuando la consulta involucra un campo que ha sido versionado, y el argumento versionConstraint está ausente.

Definir la versión por defecto para un campo

Hay varios enfoques que podemos emplear, incluyendo:

  1. Hacer obligatorio versionConstraint
  2. Usar la versión antigua por defecto hasta una determinada fecha, en la que la nueva versión se convierte en la predeterminada
  3. Usar la última versión por defecto y animar a los desarrolladores de consultas a indicar explícitamente qué versión usar

Exploremos cada una de estas estrategias y veamos sus respuestas al ejecutar esta consulta:

query GetSurname {
  account(id: 1) {
    surname
  }
}

1. Hacer obligatorio versionConstraint

Esta es la más obvia: prohibir al cliente no especificar la restricción de versión haciendo el argumento de campo obligatorio. Entonces, cuando no se proporcione, la consulta devolverá un error.

Ejecutar la consulta responderá con:

{
  "errors": [
    {
      "message": "Argument 'versionConstraint' in field 'surname' cannot be empty"
    }
  ],
  "data": {
    "account": {
      "surname": null
    }
  }
}

2. Usar la versión antigua por defecto hasta una determinada fecha en la que la nueva versión se convierte en la predeterminada

Seguir usando la versión antigua hasta una determinada fecha, cuando la nueva versión se convertirá en la predeterminada. Mientras está en este período de transición, pedir a los desarrolladores de consultas que añadan explícitamente una restricción de versión a la versión antigua antes de esa fecha mediante la nueva entrada extensions.warnings en la consulta.

Ejecutar la consulta podría responder con:

{
  "extensions": {
    "warnings": [
      {
        "message": "Field 'surname' has a new version: '2.0.0'. This version will become the default one on January 1st. We advise you to use this new version already and test that it works fine; if you find any problem, please report the issue in https://github.com/mycompany/myproject/issues. To do the switch, please add the 'versionConstraint' field argument to your query (using Composer's semver constraint rules; see https://getcomposer.org/doc/articles/versions.md#writing-version-constraints): surname(versionConstraint:\"^2.0\"). If you are unable to switch to the new version, please make sure to explicitly point to the current version '1.0.0' before January 1st: surname(versionConstraint:\"^1.0\"). In case of doubt, please contact us at name@company.com.",
    ]
  },
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

3. Usar la última versión y animar a los usuarios a indicar explícitamente qué versión usar

Usar la última versión del campo cuando versionConstraint no esté establecido, y animar a los desarrolladores de consultas a definir explícitamente qué versión debe usarse, mostrando la lista de todas las versiones disponibles para ese campo mediante una nueva entrada extensions.warnings:

Ejecutar la consulta podría responder con:

{
  "extensions": {
    "warnings": [
      {
        "message": "Field 'surname' has more than 1 version. Please add the 'versionConstraint' field argument to your query to indicate which version to use (using Composer's semver constraint rules; see https://getcomposer.org/doc/articles/versions.md#writing-version-constraints). To use the latest version, use: surname(versionConstraint:\"^2.0\"). Available versions: '2.0.0', '1.0.0'.",
    ]
  },
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

Versionado de directivas

Podemos usar las mismas estrategias para versionar directivas. Por ejemplo, al ejecutar la consulta sin proporcionar la restricción de versión:

query {
  post(by: { id: 1 }) {
    title @strTitleCase
  }
}

Podría asumir una versión por defecto a usar y producir un mensaje de aviso para que el desarrollador revise la consulta:

Consultando una directiva versionada sin restricciones de versión