Closed joelzwarrington closed 2 years ago
As well, looking into building it myself, there is an experimental warning, so curious if there are alternative approaches
We've been using Relay's ClientMutationId to achieve idemptotency since mostly this revolves around just automatically adding an argument which the client makes up a value for... which is pretty much what ClientMutationId is. Then the server just checks the key before acting on the mutation.
Here's roughly what we're doing:
class Mutations::CreateTicket < Mutations::Base
def resolve(ticket_type_id:, attributes:, client_mutation_id: nil, **_options)
if used?(client_mutation_id)
# response as if they did it, but it was already done
else
# actual mutation
end
end
def used?(key)
Ticket.where(idempotency_key: key).exists? if key
end
end
class Types::Mutation < Types::BaseObject
with_options extensions: [ClientMutationIdExtension] do |c|
c.field :create_ticket, mutation: Mutations::CreateTicket
end
end
# Similar to the default extension, but also passes the value through to the
# mutation so it can use it for deduplication.
class ClientMutationIdExtension < GraphQL::Schema::FieldExtension
def apply
field.argument :client_mutation_id, String, required: false
end
def resolve(object:, arguments:, **_rest)
yield(object, arguments, arguments[:client_mutation_id])
end
def after_resolve(value:, memo:, **_rest)
value.merge(client_mutation_id: memo)
end
end
Yep, my understanding is that Relay's clientMutationId was added for stuff like that. It looks like Shopify also uses an argument in the mutation, for example AppRevenueAttributionRecordInput.idempotencyKey
.
What do you think of using an argument for this? (Why bother with a directive?)
Let me know if you find a use case here that can't be covered by an argument!
Is your feature request related to a problem? Please describe.
As a GraphQL consumer, I'd like to safely retry an API request that might have failed due to connection issues, without causing duplication or conflicts.
Describe the solution you'd like
Idempotency is a common pattern in APIs where a consumer can retry API requests where subsequent attempts are ignored, but data could still be returned. It would be nice to have a directive built into the gem which supports this.
Something like:
Describe alternatives you've considered
None, other than building it myself
Additional context