Skip to main content

Entity ownership is an org problem wearing a schema

Federation's hardest questions are never syntax. They're "who owns Product?" — and that's a people question.

The gotcha​

Every team touching an entity adds "just one field" to it. Two years later, Product is contributed to by nine subgraphs, no team owns its lifecycle, and a rename requires a nine-team change train. Federation didn't decouple your teams; it gave them a shared object to fight over.

What actually works​

One entity, one owning subgraph. The owner defines the keys and the core identity fields. Everyone else extends with fields they genuinely resolve from their own domain:

# products subgraph — the OWNER
type Product @key(fields: "id") {
id: ID!
name: String!
category: Category!
}

# reviews subgraph — a CONTRIBUTOR
type Product @key(fields: "id") {
id: ID!
reviews(first: Int!): ReviewConnection!
averageRating: Float
}

Rules we enforce in schema review:

  • A contributing subgraph may only add fields it is the source of truth for. If the resolver just calls the owner's service, the field belongs in the owner's subgraph.
  • Keys are chosen by the owner and are stable business identifiers — never database sequence IDs that differ per environment.
  • Adding a new contributing subgraph to an entity requires the owner's review. Composition passing is not approval.

The @key trap: resolvable references​

Every @key you declare is a promise that your subgraph can resolve that entity from the key alone, at any time, under full production load. Teams declare keys casually, then discover their reference resolver:

  • does a full table scan because the key isn't indexed,
  • gets called in batches of 1 by a naive gateway plan (see N+1 at the gateway),
  • or fails for entities created before some migration.

Load-test your reference resolvers in isolation. They're the hottest code path in a federated graph and the least-tested in most codebases.

Smells that ownership has drifted​

  • A "shared" or "common" subgraph exists whose only job is holding types nobody wanted.
  • Fields whose resolver is a passthrough HTTP call to another subgraph's service.
  • Composition succeeds, but nobody can answer "if Product.name is wrong, which team do I page?"

If any of these are true, you have an ownership problem — and no amount of directives will fix it. Fix the ownership map first; the schema follows.