Skip to content

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)

graphql
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)

graphql
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:

graphql
# 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 membershipCheckout in GraphQL Explorer shows all related mutations
  • Input/payload types follow the same {Resource}{Action}Input pattern

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: membershipCheckoutDetailsUpdate vs membershipCheckout.detailsUpdate
  • Flatter schema: Root Mutation type has more fields (mitigated by alphabetical grouping)

Risks

  • Schema growth: As domains grow, the root Mutation type accumulates fields. Mitigation: The {resource} prefix groups related mutations; Explorer search and autocomplete handle large schemas well.