This has led me to taking the drastic step of making _every_ property nullable. It's gross and feels bad to use, but at least it prevents JSON parse operations from crashing applications when a value is unexpectedly null.
1. You can just keep adding new methods and fields as needed, but since each client asks only for the fields specific to what they want, you don't get big bloated response objects.
2. Lots of times your breaking changes only differ slightly from previous versions, and the way GraphQL resolvers are written makes it really easy to refactor things into one base method that both the old and new versions can share.
3. Proper use of the @deprecated schema directive means your doc is 'clean' by always showing the latest version that new users should adopt, but the doc is still there for users on older versions.
4. It's really easy to add logging and tracing in your resolvers to see how often fields are being accessed and who is using them. At some point you may decide to break backwards compatibility by deleting old fields, but you'll know exactly who you are breaking.
- Moving JSON fields in and out of nestings didn't seem to be counted as a breaking change.
- Changes were rarely announced, and there was never a changelog as to what had changed (they look to have started one starting 2018 [1])
- When we contacted support about a brake, they would often be surprised.
- Often the only sign there would be a change would be that new fields would start to show up before a larger change.
All this would happen every few months. Reading this article I can start to see the reasons why this was happening.
[1] https://developers.shopify.com/changelog?filter=all