New response format (June 2023) #69
Replies: 2 comments 9 replies
-
👋 First off, thank you for exploring the topic of streaming response in GraphQL and pushing the proposal forward over the past several years. At Shopify, we’ve recently begun exploring I see significant benefits to the new design, but also some space for possible refinements. I think the most important aspect of the new design is enforcing no duplicate delivery of any fields. This does add some complexity to the backend—in diffing the different branches and finding the overlapping leaves—but that is a cost that only needs to be paid once per query. I know past discussions considered merging fragments together to a single deferred response, so I’m glad to see this proposal allows each deferred fragment to be delivered independently. Where I would like to understand the proposal more is around the Cache keyIn past discussions, it was suggested that the usage of
It isn’t clear to me why State managementIn the proposed design, clients who receive an Improving
|
Beta Was this translation helpful? Give feedback.
-
Thank you so much Rob!! What a milestone to celebrate! Can we revisit the decision of throwing away the entire defer fragment if it sets a field that's delivered previously? An important principle we should consider always follow is that a query executed with @defer/@stream should always deliver the same amount of data as the same query without @defer/@stream. AKA the amount of the information should be the same, despite the format difference. In your example below, if we executed the query without @defer, we would have delivered "anotherField" when an error occurs on "qux". However, when we apply @defer to the query, we no longer have "anotherField".
|
Beta Was this translation helpful? Give feedback.
-
Over the past few months the incremental delivery working group has been working on the issues of data duplication, response amplification, and data consistency. The result of this is an updated response format for the
@defer
&@stream
proposal, which contains some significant differences from the previous iterations. We would now like to get wider feedback on this proposal.Features
The
incremental
array is the actual data to be applied to the response, while thepending
andcompleted
arrays return "metadata" about the execution. They are used to inform clients that defers are being executed and when all fields for that defer have been delivered.To ensure consistency, clients are expected to process all objects in the
incremental
array for a given payload before re-rendering the associated UIs.Defer Examples
Example A
Overlapping fields from initial payload are not sent in subsequent payloads. Fragment consistency is preserved. Even if "MyFragment" is ready earlier, it is not sent until "j" is also ready.
Example Result:
Example A2
Overlapping fields from parent defers are also not duplicated
Example Result:
Example B
Overlapping fields e & f are sent with whichever fragment completes first, and not sent in subsequent payloads.
Example Result when
potentiallySlowFieldA
completes first:Example Result when
potentiallySlowFieldB
completes first:Example D
Non-overlapping fields in child selections of overlapping fields are sent in separate incremental objects.
Example Result
Example F
Defer fragments with no fields are skipped entirely
Example Result
Example G
Deferred fragments at the same path are delivered independently
Example Result
Example H
If a field in a subsequent defer nulls a previously sent field due to null bubbling, the entire fragment will not be delivered. Clients should treat this fragment similar to a fragment that is
@skip(if: true)
.Assume
baz
resolves beforequx
and qux is non-nullable and returns null.Example Result
Stream Examples
Example I
Example J
If after some list fields are streamed:
A "completed" object is sent for the stream with the errors and no more streamed results will be sent
Solution Criteria Evaluation
FAQ's
Why not
done: true
onincremental
instead ofcompleted
?It's possible for a single incremental object result to "complete" more than one deferred fragment. In this example assume
baz
takes longer to resolve than the other fields.Example Response
Should
incremental[i].id
be an array, since data is shared by multipleid
s?No, because then it would introduce confusion on which id is the prefix of the subpath. This single id does not signify that this is the only fragment being delivered as multiple fragments can be delivered together. We will prefer the
id
with the longestpath
inpending
to minimize the size of the subpath.Regarding example H, I'm not sure why "anotherField" need to suffer the casualty and not be delivered?
If we delivered the other fields in the fragments it could put clients into a bad state where they understand that all the fields from defer "B" have been delivered, but it has an object for "bar" and no result for "qux". Since the schema has a non-null constraint on "qux" this is a state that should be impossible.
We discussed options where
bar
is resent as null. Maybe there's some advanced clients that can simultaneously render both states if they occur in unrelated UI components, but if you are constructing the final reconciled object you end up either dropping the whole fragment, rendering the fragment in the above described invalid state, or remove data that has already been displayed to the user.This should only happen when non-null fields are shared across sibling defers. We were thinking this situation could potentially be addressed in the future by the client controlled nullability proposal.
Beta Was this translation helpful? Give feedback.
All reactions