Schema Structure
Structural changes to a GraphQL schema come in two categories:
-
Breaking changes may cause previously-valid queries to become invalid. For example, if you remove the
titlefield, anyone who tries to query that field will get a validation error instead of response data. -
Non-breaking changes add options to a schema without breaking previously-valid queries.
Making a breaking change can be bad news for your API clients, since their applications may break. But, sometimes they’re required. Non-breaking changes don’t affect existing queries, since they just add new parts to the schema.
Here are few tips for managing schema structure changes.
Maintain a .graphql schema dump
Make structure changes part of the normal code review process by adding a schema.graphql artifact to your project. This way, any changes to schema structure will show up clearly in a pull request as a diff to that file.
You can read about this approach in “Tracking Schema Changes with GraphQL-Ruby” or the built-in GraphQL::RakeTask for generating schema dumps.
Automatically check for breaking changes
You can use GraphQL::SchemaComparator to check for breaking changes during development or CI. If you maintain a dump of queries that typically run against your server, you may also utilize GraphQL::StaticValidation to validate these queries directly. A Rake task such as the one below can be used to identify changes that are incompatible with existing queries.
namespace :graphql do namespace :queries do desc 'Validates GraphQL queries against the current schema' task validate: [:environment] do queries_file = 'test/fixtures/files/queries.json' queries = Oj.load(File.read(queries_file)) Validate.run_validate(queries, MySchema) end module Validate def self.run_validate(queries, schema) puts '⏳ Validating queries...' puts "\n" results = queries.map { |query| schema.validate(query) } errors = results.flatten if errors.empty? puts '✅ All queries are valid' else print_errors(errors) end end def self.print_errors(errors) puts 'Detected the following errors:' puts "\n" errors.each do |error| path = error.path.join(', ') puts "❌ #{path}: #{error.message}" end end end end end