-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove the clientMutationId
requirement by creating a new root id for each executed mutation
#2349
Conversation
7218478
to
81e0fb7
Compare
@steida Mind testing this PR for your situation? (And is your situation a production app?) |
@alloy My situation is https://github.com/este/este. Not a production :( @jstejada What do you think, should I test? Will Relay team merge it? Thank you. |
@kassens What do you think? Should I test it? Is there any chance it will be merged in, say, next six months? |
@steida We should help the Relay team by testing them preemptively so that when they have time they will know it was already tested by at least somebody. |
Is there a constructive reason for the old behavior, incidentally? It does seem odd to essentially assume idempotency for all mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jstejada has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
@AndrewIngram mentions in https://graphql.slack.com/archives/C0BEXJLKG/p1531176878000009 that another work-around until this is merged is to clone the data that comes back. Anyone has such code to share here that, hopefully, avoids the long boilerplate in https://facebook.github.io/relay/docs/en/mutations.html#updater-configs? |
BTW, the same problem arises for subscriptions. In this case the fix needs to be applied in this manner, as there's no place to inject a payload sequence ID (as far as I'm aware). That said, @AndrewIngram's workaround is pretty satisfactory in practice. |
thanks for the ping @sibelius! apologies again for the delay in getting to this. We still need to discuss internally a little bit what the best approach would be here. From a short discussion, it seems like we could address separately the problem of ensuring that we don't overwrite mutation payloads vs removing the requirement for a clientMutationID. cc @josephsavona @kassens |
I just ran into something like this. We've switched our mutations to return unions between the payload type and various typed errors as their root types. When running a mutation with the same inputs leads to a different concrete return type (e.g. an error instead of a payload), we get an invalid record update due to the type mismatch, because the root field gets reused. |
@jstejada do you have any update on this particular issue ? Thanks in advance. |
i think @tyao1 already fixed this |
This has been a long time coming, thanks @robrichard for the initial PR and @tyao1 for picking this back up! |
…t practices and not purely a relay spec (#2603) Summary: Better late than never, roughly a year ago in the GraphQL Working Group 3 we took it on Artsy to start moving the Relay specs to be considered "GraphQL Best Practices" ([link](https://github.com/graphql/graphql-wg/blob/master/notes/2018-02-01.md#discuss-moving-connectionglobal-id-specs-from-relay-into-best-practices-repo)) This starts that, beginning with first minimizing the use of Relay as being the sole client that conforms to the connection and object identifier spec. Main notes: - When referring to relay on the client I switched "Relay" to "Spec-compliant client" or "client" - When referring to relay's compatibility on the server side, I oriented it to just be "spec-compliant" - I mostly left the Mutation input spec alone, now that it's only for Relay classic and is close to being removed completely from Relay #2349 - This is step one, once this is merged then I'd like to move both object identification and connection to be on the graphql repos instead of relay and I can handle link updating etc Happy to change wording etc /cc leebyron in case you don't watch this repo too closely. Pull Request resolved: #2603 Reviewed By: josephsavona Differential Revision: D19071019 Pulled By: jstejada fbshipit-source-id: cf5a7dfb93cfd565d4f178a51cdff7cd0caec1a0
…or each executed mutation (#2349) Summary: Fixes #1734 Fixes #2333 Related to relayjs/relay-examples#30 When a mutation is executed, it's payload is added to the store using a key of the serialized arguments. In the Todo Modern example this looks like this: ```js { 'client:root:addTodo(input:{"text":"a"})': { ... } // added by mutation response 'client:VXNlcjptZQ==:__TodoList_todos_connection': { edges: [ __refs: [ 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:0', 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:1', 'client:root:addTodo(input:{"text":"a"}):todoEdge', // added by mutation imperative updater function ] ] } } ``` If you execute a second `addTodo` mutation with the same arguments, the `client:root:addTodo(input:{"text":"a"})` field in the store will get overwritten with the second mutation and the store will look like this: ```js { 'client:root:addTodo(input:{"text":"a"})': { ... } 'client:VXNlcjptZQ==:__TodoList_todos_connection': { edges: [ __refs: [ 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:0', 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:1', 'client:root:addTodo(input:{"text":"a"}):todoEdge', 'client:root:addTodo(input:{"text":"a"}):todoEdge', ] ] } } ``` When React renders the data, you will have the latter `todo` in the connection array twice and will likely get an error along the lines of `Encountered two children with the same key, <id>. Child keys must be unique; when two children share a key, only the first child will be used`. This was [fixed in relay-examples](relayjs/relay-examples@ed95594#diff-4b42725e6eb503f56da8e3f637e9a4e3) by adding a `clientMutationId` to the mutation arguments. I don't think this is ideal since 1. it requires the developer to manually add this to each mutation 2. it places an additional constraint on the GraphQL schema to support this field. The approach I took to fix this, was to use a new root `dataID` (instead of the static `client:root`) for results on each mutation. This will namespace results from each mutation so they can never overwrite one another. With these changes, the relay store now looks like this after completing the same series of mutations: ```js { 'client:mutationRoot0:addTodo(input:{"text":"a"})': { ... }, 'client:mutationRoot1:addTodo(input:{"text":"a"})': { ... }, 'client:VXNlcjptZQ==:__TodoList_todos_connection': { edges: [ __refs: [ 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:0', 'client:VXNlcjptZQ==:__TodoList_todos_connection:edges:1', 'client:mutationRoot0:addTodo(input:{"text":"a"}):todoEdge', 'client:mutationRoot1:addTodo(input:{"text":"a"}):todoEdge' ] ] } } ``` I tested this and it works to solve this issue, but I'm unsure if there could be any unintended consequences by this change. Closes #2349 Reviewed By: josephsavona Differential Revision: D7631345 Pulled By: tyao1 fbshipit-source-id: fc53638536860855f271f436f810caa0fc60bcb0
…t practices and not purely a relay spec (#2603) Summary: Better late than never, roughly a year ago in the GraphQL Working Group 3 we took it on Artsy to start moving the Relay specs to be considered "GraphQL Best Practices" ([link](https://github.com/graphql/graphql-wg/blob/master/notes/2018-02-01.md#discuss-moving-connectionglobal-id-specs-from-relay-into-best-practices-repo)) This starts that, beginning with first minimizing the use of Relay as being the sole client that conforms to the connection and object identifier spec. Main notes: - When referring to relay on the client I switched "Relay" to "Spec-compliant client" or "client" - When referring to relay's compatibility on the server side, I oriented it to just be "spec-compliant" - I mostly left the Mutation input spec alone, now that it's only for Relay classic and is close to being removed completely from Relay #2349 - This is step one, once this is merged then I'd like to move both object identification and connection to be on the graphql repos instead of relay and I can handle link updating etc Happy to change wording etc /cc leebyron in case you don't watch this repo too closely. Pull Request resolved: #2603 Reviewed By: josephsavona Differential Revision: D19071019 Pulled By: jstejada fbshipit-source-id: cf5a7dfb93cfd565d4f178a51cdff7cd0caec1a0
Can someone please comment with respect to the relevance of this PR to the following, taken from the "GraphQL Server Specification" section of the documentation?
Is it correct to say that this documentation is incorrect because client mutation identifiers no longer exist? Or do they exist but they're an internal API (e.g., auto-generated)? Or something else...? |
It is still a common pattern, but not a requirement anymore |
any guidance on how to implement the mutation identifier when writing a compatible server? |
If you're using JS, graphql-relay provides primitives for this. Otherwise, many other frameworks have their own equivalents. It just depends on which server you're using. |
clientMutationId is like an idempotence key (https://stripe.com/docs/api/idempotent_requests) so your server can validate that user hasn't sent the same mutation twice |
not exactly – it was originally meant for clients to be able to correlate mutation payloads with mutations, without assuming request/response semantics on the network transport |
Fixes #1734
Fixes #2333
Fixes #825
Related to relayjs/relay-examples#30
When a mutation is executed, it's payload is added to the store using a key of the serialized arguments. In the Todo Modern example this looks like this:
If you execute a second
addTodo
mutation with the same arguments, theclient:root:addTodo(input:{"text":"a"})
field in the store will get overwritten with the second mutation and the store will look like this:When React renders the data, you will have the latter
todo
in the connection array twice and will likely get an error along the lines ofEncountered two children with the same key, <id>. Child keys must be unique; when two children share a key, only the first child will be used
.This was fixed in relay-examples by adding a
clientMutationId
to the mutation arguments. I don't think this is ideal sinceThe approach I took to fix this, was to use a new root
dataID
(instead of the staticclient:root
) for results on each mutation. This will namespace results from each mutation so they can never overwrite one another.With these changes, the relay store now looks like this after completing the same series of mutations:
I tested this and it works to solve this issue, but I'm unsure if there could be any unintended consequences by this change.