Description
Hey everyone,
I spoke about this at GraphQL Europe this past weekend and would like to take the discussion of how to solve the issue that I spoke about here. Overall, I'm unsure if the solution is to extend directives to do what I proposed (and therefore Arguments), to introduce a new construct for fragments, or if there are other ways to solve this problem entirely.
Description of the Problem
Fundamentally, queries and subscriptions are two separate operations. Therefore if there is a subscription that is created as the result of a query, or a subscription that needs to be re-created as the result of changed data from another subscription, there could be dropped events in the gap between the two operations. Below is a diagram I used in my presentation between a client and a server with the network in the middle:
We've currently circumvented this by adopting versioned responses for both queries and subscriptions, thus replaying any dropped events on the subscription itself once it is setup. However ideally I would like this to be a single atomic operation, thus looking more like this:
I quite like subscriptions as the body of the subscription can effectively describe any type of operation for the client to perform; be it a direct data update, a data manipulation, or a description of operations to perform on data. Thus I do not believe that @live
directives or similar truly solve this issue for all cases that subscriptions can.
Proposed Solutions for Discussion
- Have a
@subscribeTo
directive that references the subscription body fragment:
fragment userFragment on User
@subscribeTo(name: "avatarChange", with: avatarChangeFragment)
{
name
username
avatarUrl(size: 200)
avatarChangeId # can be an implicit field on this fragment added by
# the server to track subscription events on the client
}
fragment avatarChangeFragment on UserAvatarChangePayload {
user {
avatarUrl(size: 200)
}
}
As directives are using Arguments
inside them, this change would require Arguments
to be able to reference other fragments which could have adverse effects for Arguments
used in any other context and therefore I'm not entirely sure that extending Arguments
is the right approach here.
- Create a new extension to the language dedicated to this use case:
fragment userFragment on User
subscribeTo(name: "avatarChange", with: avatarChangeFragment)
{
name
username
avatarUrl(size: 200)
avatarChangeId # can be an implicit field on this fragment added by
# the server to track subscription events on the client
}
fragment avatarChangeFragment on UserAvatarChangePayload {
user {
avatarUrl(size: 200)
}
}
This proposal doesn't touch Arguments
as it's a whole new extension. However it does introduce new concepts to the GraphQL spec which come with its own considerations.
- Potentially leverage existing subscriptions, if the root field matches the fragment type:
fragment userFragment on User
subscribeTo(userAvatarChangeSubscription)
{
name
username
avatarUrl(size: 200)
avatarChangeId # can be an implicit field on this fragment added by
# the server to track subscription events on the client
}
subscription userAvatarChangeSubscription {
userAvatarChange # somehow linked to the previous fragment,
# for each occurrence of the previous fragment in its query
{
avatarUrl(size: 200)
}
}
I personally like a solution like this the best as it can leverage existing constructs. However it's unclear how the subscription can be linked to each occurrence of the fragment in the query.
- ???
Next Steps
I've opened this issue for discussion purposes before hashing out whatever needs to be changed in the spec itself. Let me know your thoughts, concerns, questions, and ideas.
Looking forward to solving this issue, simplifying our stack, and offering the community with a path forward on these types of issues!