Open gwardwell opened 1 month ago
Edited to:
NIT: If we are adding new @deprecatedX
directives I'd personally vote to make reason
non-nullable. I understand that proposal was made to make it close to existing @deprecated
directive.... but current behavior is IMHO very confusing as it is possible for folks to specify @deprecated(reason: null)
. See https://github.com/graphql/graphql-spec/issues/53#issuecomment-1688335159 for more details.
@dariuszkuc I love it. Historically I've solved that with a custom linting rule to enforce a deprecation reason. I love the idea of non-null with a default value for backwards compatibility, too. I'll update the issue to reflect this.
Edit: I have updated the issue description to add info on making the deprecation reason required.
Thanks for your work on this Greg! The best way forward is to break this up into smaller chunks and get each of them through the RFC process - you can do that under the umbrella of this issue, but in general we prefer smaller isolated changes that can be discussed separately; the needs of union member deprecation are likely to have different issues to that of unions themselves, for example. Something like @deprecatedImplementation
should be discussed in isolation without slowing down progress on other clearer/cleaner changes.
The first thing I'd recommend you do is to bring this to the next GraphQL Working Group:
https://github.com/graphql/graphql-wg/blob/main/agendas/2024/11-Nov/07-wg-primary.md
Discussion there will help give you guidance on how to proceed (and surface any immediate roadblocks).
Current State
GraphQL schema updates should be as self-service as possible to reduce bottlenecks for change. In addition to adding new schema, schema must be replaced/removed over time.
The typical way this is achieved is through deprecation via the
@deprecated
directive:The existing
@deprecated
directive provides deprecation information as part of introspection queries. This information is also often surfaced in IDE and platform tooling and used to communicate pending removal of schema to clients when they perform operations.Existing efforts
There is currently an open PR for supporting the deprecation of objects, but there has been little movement in recent days. This PR is also insufficient to cover all the use cases that need to be addressed.
Problem Statement
The problem is that the
@deprecated
directive is solely focused on the clients performing operations and only valid on a very limited subset of locations:Not only are these locations insufficient for the client use case, leaving gaps that prove challenging to overcome when attempting to make schema changes, they also completely miss the use case for coordinating work in a distributed system.
Additionally, the
@deprecated
directive does not require a reason, which does not add clarity as to why something is being removed. This can also be addressed.Client Side Deprecation Gaps
GraphQL schema needs the ability to communicate when object-level schema is intended to be removed. This is true for use cases like:
In both cases, deprecating the field that returns the union or interface may not be an option if the goal is only to remove an interface implementer or member of the union. A separate solution is necessary.
This is a fairly unique use case to other languages because of the public API that GraphQL represents. Other languages do not expose union members or interface implementations in the same way as GraphQL — which appear in fragments within client operations.
Let's dig into the problem a bit deeper.
Removing an Interface Implementation
Interface implementers can appear as inline or named fragments within an operation. For example:
Removing a Union Member
Similar to interfaces, Union members can appear as inline or named fragments within an operation. For example:
Service Deprecation Use Case
The service developer use case is currently completely ignored. With the creation of the Composite Schema Working Group, focus is starting to shift to enabling the distributed management of GraphQL. To manage schema at scale, we need a way to coordinate change at scale. This requires evolution of our tooling and provides an opportunity for deprecation to step beyond service-and-client communication to service-and-service communication.
This will require adding support for deprecation to additional locations:
This use case is covered in many programming languages which allow things like interfaces, classes, etc. to be annotated as deprecated.
Scalar deprecation
Object deprecation
Interface deprecation
Input deprecation
Union deprecation
Enum deprecation
Lack of Deprecation Clarity
The current
@deprecated
directive definition defines thereason
argument as optional:This means that schema can be deprecated with no additional clarity as to why:
Worse yet, the
reason
argument can accept a null value, which is a different type of lack of clarity:Proposal
Make the
@deprecated
Directive'sreason
Argument RequiredThe
@deprecated
directive'sreason
argument should be requires. Providing a default value will allow it to be backwards compatible with existing schemas (see https://github.com/graphql/graphql-spec/issues/53#issuecomment-1688335159 for additional context).Allow the Deprecation of Union Members and Interface Implementations
Option 1: Expand Directive Locations To Union Members and Interface Implementations
The directive locations could be expanded to allow applying directives to union members and interface implementations (credit). This would allow the
@deprecated
directive to be applied to the union member and interface implementation directly. The resulting API would look something like this:This would avoid built-in directive bloat while still providing the intended value.
Option 2: Add
@deprecatedMember
and@deprecatedImplementation
DirectivesIf expanding directive locations becomes a non-option, new directives could be added to the spec to allow the deprecation of union members and interface implementations, which would be applied to the host (the union or the object type):
@deprecatedMember
and@deprecatedImplementation
@deprecatedMember
DirectiveThe
@deprecatedMember
directive would define a member of a union that will be removed at some point in the future. The proposed definition would look like this:The directive would be applied to a union and define the member that will be removed along with an optional reason. For example:
When a client performs an operation against the union and mentions the deprecated member by name, client tooling would understand the deprecated use in much the same way as it understands deprecation on a field:
@deprecatedImplementation
DirectiveSimilar to
@deprecatedMember
, the@deprecatedImplementation
directive would define an interface implemented on an object that will be removed at some point in the future. The proposed definition would look like this:The directive would be applied to an object and define an implemented interface that will be removed along with an optional reason. For example:
When a client performs an operation against the interface and mentions the deprecated implementing object by name, client tooling would understand the deprecated use in much the same way as it understands deprecation on a field:
Expand
@deprecated
Locations for Service Use CasesTo cover the outlined service use cases, the
@deprecated
directive's locations can be expanded to object-level schema like Object Types, Interfaces, Scalars, etc. This is consistent with other languages that allow classes, interfaces, etc. to be annotated as deprecated.This would allow deprecation of all schema locations that can currently accept a directive, which will give the most flexibility for communicating change within a Graph.
Next Steps
I would love to get this work moving to add full support for deprecation to the GraphQL spec. I'd love comments/feedback and any guidance for introducing this to the spec that folks can provide.