ADR-002: Mutation Naming Pattern
Status
Accepted
Context
When designing GraphQL mutations, there are two common patterns for organizing related operations:
Option A: Flat Prefixed Mutations (Shopify Pattern)
type Mutation {
membershipCheckoutCreate(input: MembershipCheckoutCreateInput!): MembershipCheckoutPayload!
membershipCheckoutDetailsUpdate(
input: MembershipCheckoutDetailsUpdateInput!
): MembershipCheckoutPayload!
membershipCheckoutAddressUpdate(
input: MembershipCheckoutAddressUpdateInput!
): MembershipCheckoutPayload!
}Used by Shopify and GitHub.
Option B: Namespaced Mutations (Apollo Pattern)
type Mutation {
membershipCheckout: MembershipCheckoutMutations!
}
type MembershipCheckoutMutations {
create(input: CreateInput!): Payload!
detailsUpdate(input: DetailsUpdateInput!): Payload!
addressUpdate(input: AddressUpdateInput!): Payload!
}Documented by: Apollo GraphQL
We needed to decide which pattern to adopt for our federated GraphQL API.
Decision
We adopt flat prefixed mutations (Option A).
Rationale
1. GraphQL Spec Compliance
The GraphQL specification states:
"Resolution of fields other than top-level mutation fields must always be side effect-free and idempotent."
Apollo's own documentation acknowledges this caveat. Namespaced mutations technically violate the spec because the actual mutating fields are nested, not at the root level.
2. Serial Execution Guarantees
The GraphQL spec guarantees that root-level mutation fields execute serially, not in parallel. This prevents race conditions when multiple mutations affect the same resource.
With namespacing, nested mutation fields execute in parallel, which can cause race conditions:
# Flat pattern: serial execution
mutation {
membershipCheckoutCreate(input: {...}) { ... }
membershipCheckoutDetailsUpdate(input: {...}) { ... } # waits for create
}
# Namespaced pattern: parallel execution (race condition)
mutation {
membershipCheckout {
create(input: {...}) { ... }
detailsUpdate(input: {...}) { ... } # runs in parallel with create
}
}3. Federation Simplicity
In Apollo Federation, flat mutations are simpler to distribute across subgraphs. Each subgraph owns its mutations directly at the root level. Namespaced mutations require coordinating shared namespace types across subgraphs, adding complexity.
4. Industry Adoption
The flat pattern is used at scale by Shopify and GitHub.
5. Equivalent Discoverability
The flat pattern provides the same organizational benefits as namespacing:
- All
membershipCheckout*mutations sort together alphabetically - Typing
membershipCheckoutin GraphQL Explorer shows all related mutations - Input/payload types follow the same
{Resource}{Action}Inputpattern
Consequences
Positive
- Spec-compliant: Mutations execute serially as the spec intends
- No race conditions: Sequential execution prevents data corruption
- Simple federation: Each subgraph owns its mutations directly
- Proven at scale: Pattern used by largest GraphQL APIs in production
- Good tooling support: Works well with codegen, caching, persisted queries
Negative
- Longer mutation names:
membershipCheckoutDetailsUpdatevsmembershipCheckout.detailsUpdate - Flatter schema: Root
Mutationtype has more fields (mitigated by alphabetical grouping)
Risks
- Schema growth: As domains grow, the root
Mutationtype accumulates fields. Mitigation: The{resource}prefix groups related mutations; Explorer search and autocomplete handle large schemas well.