ardatan/graphql-mesh

Feature Request: Operation Versioning

afigard opened this issue · 1 comments

Context

In complex API ecosystems, different versions of operations are often required to support backward compatibility and evolution. This feature request aims to support operation versioning in GraphQL Mesh.

Currently, GraphQL Mesh merges all versions of an operation into a single query, which can result in conflicts and loss of version-specific details. By enabling operation versioning, each version of an operation will be exposed as a distinct query in the generated supergraph. This allows clients to explicitly request specific API versions, ensuring compatibility and leveraging new features without breaking existing functionality.

Motivational Example

Given the following swaggers:
suppliers_v1.json;

{
  "openapi": "3.0.0",
  "info": {
    "title": "Suppliers API",
    "version": "1.0.0"
  },
  "paths": {
    "/suppliers/{id}": {
      "get": {
        "operationId": "getSupplierById",
        "summary": "Get a supplier by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The supplier ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Supplier"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Supplier": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        }
      }
    }
  }
}

suppliers_v2.json;

{
  "openapi": "3.0.0",
  "info": {
    "title": "Suppliers API",
    "version": "2.0.0"
  },
  "paths": {
    "/suppliers/{id}": {
      "get": {
        "operationId": "getSupplierById",
        "summary": "Get a supplier by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The supplier ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Supplier"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Supplier": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          }
        }
      }
    }
  }
}

And the following mesh.config.ts file;

import { defineConfig } from "@graphql-mesh/compose-cli";
import { loadOpenAPISubgraph } from "@omnigraph/openapi";

export const composeConfig = defineConfig({
  subgraphs: [
    {
      sourceHandler: loadOpenAPISubgraph("suppliers@1", {
        source: "./suppliers_v1.json",
      }),
    },
    {
      sourceHandler: loadOpenAPISubgraph("suppliers@2", {
        source: "./suppliers_v2.json",
      }),
    },
  ],
});

The supergraph.graphql output by Mesh will only have one query: getSupplierById.

We propose to add a operationVersioning parameter to the defineConfig, so that, by using it in the mesh.config.ts file like this;

import { defineConfig } from "@graphql-mesh/compose-cli";
import { loadOpenAPISubgraph } from "@omnigraph/openapi";

export const composeConfig = defineConfig({
  subgraphs: [
    {
      sourceHandler: loadOpenAPISubgraph("suppliers@1", {
        source: "./suppliers_v1.json",
      }),
    },
    {
      sourceHandler: loadOpenAPISubgraph("suppliers@2", {
        source: "./suppliers_v2.json",
      }),
    },
  ],
  operationVersioning: true,
});

The supergraph.graphql output by Mesh will now have two queries: getSupplierById_v1 and getSupplierById_v2 which will respectively return the distinct objects Supplier_v1 and Supplier_v2.

GraphQL Mesh will automatically try to merge types and fields. In order to prevent conflicts, transforms can help in my opinion;
https://the-guild.dev/graphql/mesh/v1/transforms/prefix
But let me know if I miss something with your case.