Alias types in GraphQL Schema Definition Language
Asked Answered
G

2

10

I have the following graphql schema definition in production today:

type BasketPrice {
  amount: Int!
  currency: String!
}

type BasketItem {
   id: ID!
   price: BasketPrice!
}

type Basket {
   id: ID!
   items: [BasketItem!]!
   total: BasketPrice!
}

type Query {
   basket(id: String!): Basket!
}

I'd like to rename BasketPrice to just Price, however doing so would be a breaking change to the schema because clients may be referencing it in a fragment, e.g.

fragment Price on BasketPrice {
   amount
   currency
}

query Basket {
   basket(id: "123") {
      items {
         price {
            ...Price
         }
      }
      total {
         ...Price
      }
   }
}

I had hoped it would be possible to alias it for backwards compatibility, e.g.

type Price {
  amount: Int!
  currency: String!
}

# Remove after next release.
type alias BasketPrice = Price;

type BasketPrice {
  amount: Int!
  currency: String!
}

type BasketItem {
   id: ID!
   price: BasketPrice!
}

type Basket {
   id: ID!
   items: [BasketItem!]!
   total: BasketPrice!
}

type Query {
   basket(id: String!): Basket!
}

But this doesn't appear to be a feature. Is there a recommended way to safely rename a type in graphql without causing a breaking change?

Goosefish answered 4/7, 2019 at 17:27 Comment(0)
M
2

I want this too, and apparently we can't have it. Making sure names reflect actual semantics over time is very important for ongoing projects -- it's a very important part of documentation!

The best way I've found to do this is multi-step, and fairly labor intensive, but at least can keep compatibility until a later time. It involves making input fields optional at the protocol level, and enforcing the application-level needs of having "one of them" at the application level. (Because we don't have unions.)

input OldThing {
   thingId: ID!
}

input Referee {
  oldThing: OldThing!
}

Change it to something like this:

input OldThing {
   thingId: ID!
}

input NewThing {
  newId: ID!
}

input Referee {
  oldThing: OldThing @ deprecated(reason: "Use newThing instead")
  newThing: NewThing
}

In practice, all old clients will keep working. You can update your handler code to always generate a NewThing, and then use a procedural field resolver to copy it into oldThing if asked-for (depending on which framework you're using.) On input, you can update the handler to always translate old to new on receipt, and only use the new one in the code. You'll also have to return an error manually if neither of the elements are present.

At some point, clients will all be updated, and you can remove the deprecated version.

Maul answered 27/10, 2020 at 17:38 Comment(2)
A couple years later and this is seemingly still the case.Arlynearlynne
@Arlynearlynne I know, right!Maul
H
0

There's no way to rename a type without it being a breaking change for the reasons you already specified. Renaming a type is a superficial change, not a functional one, so there's no practical reason to do this.

The best way to handle any breaking change to a schema is to expose the new schema on a different endpoint and then transition the clients to using the new endpoint, effectively implementing versioning for your API.

The only other way I can think of getting around this issue is to create new fields for any fields that utilize the old type, for example:

type BasketItem {
   id: ID!
   price: BasketPrice! @ deprecated(reason: "Use itemPrice instead")
   itemPrice: Price!
}

type Basket {
   id: ID!
   items: [BasketItem!]!
   total: BasketPrice! @ deprecated(reason: "Use basketTotal instead")
   basketTotal: Price!
}
Homology answered 4/7, 2019 at 19:46 Comment(5)
The practical reason I want to rename the type is so other developers can understand the code. If naming didn't matter than I'd just call every type T1, T2, T3 etc. I always thought a benefit of gql over, for example REST, is you don't have to version your API as you can just add new fields without affecting clients using @deprecated fields, had hoped the same would be true for type names.Goosefish
I didn't mean to imply that naming itself isn't important -- it is -- but there's a fine line between renaming something to convey meaning and renaming for aesthetic purposes. I can understand, however, how if you want to extend the usage of the BasketPrice type to things other than a basket (for example) then continuing to name the type BasketPrice is undesirable.Homology
Versioning can be avoided much more easily with GraphQL, but there are changes that are breaking changes and if you want to implement those, versioning is effectively necessary. While you can't deprecate a type like you can a field, renaming a field is just as much a breaking change as renaming a type.Homology
@riscarrott Updated the answer, since there is technically one other way to work around this limitation without actually breaking the schemaHomology
I think "there's no practical reason to rename things" is just wrong -- names are documentation. Names are often the most important part of documentation! This is why there's a lot of truth to "naming things" being one of the two hard things in computer science.Maul

© 2022 - 2024 — McMap. All rights reserved.