Extensibility
- Root Query - Spec | Docs
- Mutation - Spec | Docs
- Object Type - Spec | Docs
- Arguments - Spec | Docs
- Input Object - Spec | Docs
- Output Object: An Object Type used as the response type for a mutation (primarily to support extensibility)
It should be possible to make the following changes to a schema, without introducing breaking changes:
- Add a new root query (backwards-compatible)
- Add a new mutation (backwards-compatible)
- Add optional arguments to a query/mutation (some backwards-compat risks)
- Add extra data to the output of a mutation (backwards-compatible)
The following patterns should be followed to ensure our schema remains extensible, with minimal (ideally no) breaking changes.
All mutations should return an "Output Object," rather than some concrete type. An "Output Object" is just an Object Type with the specific purpose of returning data and metadata related to a mutation.
It is not possible for a client to send a mutation and separate root queries in the same request. Because of this, it's critical that the output of a mutation be able to add more data over time, as client needs expand.
type Mutation {
createFoo: Foo
}
type Mutation {
createFoo: FooOutput
}
type ExampleOutput {
# Extensions (and core) can extend with more fields at a later date
foo: Foo
}
If a query or mutation accepts (or will likely accept) > 1 argument, an Input Object should be used instead, and given the argument name input
.
When using a query or mutation. it's common for clients to create Named Operation Definitions. When a query/mutation takes several arguments, the types (and their defaults) have to be kept in sync with the schema:
query ClientGetFooOperationNotNice(
$arg1: String!
$arg2: Int
# If the schema has a default value, it won't be used unless it's re-defined here
$arg3: String = "test"
) {
getFoo(
arg1: $arg1
arg2: $arg2
arg3: $arg3
) {
# field selection
}
}
Instead, using an Input Object, this can be simplified without loss of functionality:
query ClientGetFooOperationNice($input: GetFooInput) {
# Note the client no longer has to manually keep operation arg definitions
# and default schema values in sync
getFoo(input: $input) {
# field selection
}
}
type Query {
getFoo(
arg1: String!
arg2: Int
arg3: String = "test"
)
}
type Query {
getFoo(input: GetFooInput): Foo
}
input GetFooInput {
# Extensions (and core) can add additional fields if they are nullable/optional
arg1: String!
arg2: Int
arg3: String = "test"
}
Magento Framework, unlike many GraphQL server frameworks, allows extending both arguments lists and Input Object types. This can be powerful, but can also easily become a source of backwards-incompatible changes.
When adding a new field to an arguments list or Input Object type, it should:
- Be optional/nullable
- Have identical resolver logic when the new field is not provided by a client
When modifying arguments lists or Input Object Types, you should not
- Change the types of any existing arguments/input object fields
- Change the nullability of any existing arguments/input object fields
- Change the name of any existing arguments/input object fields