Concetti, idee, strategie
Concetti, idee, strategieStrategie per il versionamento di campi e direttive

Strategie per il versionamento di campi e direttive

Leggi prima la guida Far evolvere lo schema tramite il versionamento dei campi, che spiega la funzionalità di "field versioning" in Gato GraphQL.

Gato GraphQL consente a campi e direttive di ricevere l'argomento versionConstraint, per scegliere quale versione specifica (cioè l'implementazione) del campo/della direttiva utilizzare:

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

Cosa dovrebbe accadere quando non specifichiamo l'argomento versionConstraint? Per esempio, a quale versione dovrebbe risolversi il campo surname nella query qui sotto?

query GetSurname {
  account(id: 1) {
    # Quale versione dovrebbe essere usata? 1.0.0? 2.0.0?
    surname
  }
}

Qui abbiamo due preoccupazioni:

  1. Decidere quale sia la versione predefinita da utilizzare quando non ne viene fornita alcuna
  2. Informare il client che ci sono diverse versioni tra cui scegliere

Prima di affrontare queste preoccupazioni, dobbiamo scoprire quanto bene GraphQL fornisce un feedback contestuale durante l'esecuzione di una query.

Fornire un feedback contestuale durante l'esecuzione delle query

Dobbiamo sottolineare una circostanza tutt'altro che ideale con GraphQL in questo momento: non offre buone informazioni contestuali durante l'esecuzione delle query. Questo è evidente per quanto riguarda le deprecazioni, dove i dati di deprecazione vengono mostrati solo tramite l'introspezione interrogando i campi isDeprecated e deprecationReason sui tipi Field ed Enum:

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

La risposta sarà:

{
  "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
        }
      ]
    }
  }
}

Tuttavia, durante l'esecuzione di una query che coinvolge un campo deprecato…

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

...le informazioni di deprecazione non appariranno nella risposta:

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

Ciò significa che lo sviluppatore che esegue la query deve eseguire attivamente query di introspezione per scoprire se lo schema è stato aggiornato e se qualche campo è stato deprecato. Questo potrebbe accadere… una volta ogni tanto? Forse mai?

Sarebbe un grande miglioramento ai fini della revisione delle query obsolete se l'API GraphQL fornisse informazioni di deprecazione durante l'esecuzione di query che coinvolgono campi deprecati. Idealmente queste informazioni potrebbero essere fornite sotto una nuova voce di primo livello deprecations, che appare dopo errors e prima di data (seguendo il suggerimento della spec per il formato della risposta).

Poiché una voce di primo livello deprecations non fa parte della spec, la funzionalità "Proactive Feedback" di Gato GraphQL aggiunge il supporto per un feedback migliore nella risposta alla query utilizzando la voce di primo livello generica extensions, che consente di estendere il protocollo secondo necessità:

Informazioni di deprecazione nella risposta alla query

Pubblicizzare le versioni tramite avvisi

Abbiamo appena appreso che il server GraphQL può utilizzare la voce di primo livello extensions per fornire le deprecazioni. Possiamo usare questa stessa metodologia per aggiungere una voce warnings, in cui informiamo lo sviluppatore che un campo è stato versionato. Non forniamo queste informazioni sempre; solo quando la query coinvolge un campo che è stato versionato e l'argomento versionConstraint è assente.

Definire la versione predefinita di un campo

Esistono diversi approcci che possiamo adottare, tra cui:

  1. Rendere versionConstraint obbligatorio
  2. Utilizzare la vecchia versione per impostazione predefinita fino a una certa data, alla quale la nuova versione diventa quella predefinita
  3. Utilizzare l'ultima versione per impostazione predefinita e incoraggiare gli sviluppatori di query a indicare esplicitamente quale versione utilizzare

Esploriamo ciascuna di queste strategie e vediamo le loro risposte durante l'esecuzione di questa query:

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

1. Rendere versionConstraint obbligatorio

Questo è il più ovvio: vietare al client di non specificare il vincolo di versione rendendo obbligatorio l'argomento del campo. Quindi, ogni volta che non viene fornito, la query restituirà un errore.

L'esecuzione della query risponderà con:

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

2. Utilizzare la vecchia versione per impostazione predefinita fino a una certa data alla quale la nuova versione diventa quella predefinita

Continuare a utilizzare la vecchia versione fino a una certa data, in cui la nuova versione diventerà quella predefinita. Durante questo periodo di transizione, chiedere agli sviluppatori di query di aggiungere esplicitamente un vincolo di versione alla vecchia versione prima di tale data tramite la nuova voce extensions.warnings nella query.

L'esecuzione della query potrebbe rispondere 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. Utilizzare l'ultima versione e incoraggiare gli utenti a indicare esplicitamente quale versione utilizzare

Utilizzare l'ultima versione del campo quando versionConstraint non è impostato, e incoraggiare gli sviluppatori di query a definire esplicitamente quale versione deve essere utilizzata, mostrando l'elenco di tutte le versioni disponibili per quel campo tramite una nuova voce extensions.warnings:

L'esecuzione della query potrebbe rispondere 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"
    }
  }
}

Versionamento delle direttive

Possiamo utilizzare le stesse strategie per versionare le direttive. Per esempio, durante l'esecuzione della query senza fornire il vincolo di versione:

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

Potrebbe assumere una versione predefinita da utilizzare e produrre un messaggio di avviso affinché lo sviluppatore riveda la query:

Interrogare una direttiva versionata senza vincoli di versione