Skip to content

CONCEPT Cited by 1 source

Entity ID convention

Definition

An entity ID convention is a project-wide rule that every identifier exposed on an API boundary follows the same structural pattern, typically <prefix>:<type>:<natural-id> or similar, such that:

  • The type of the referenced entity is recoverable from the ID string alone (no out-of-band type hint required).
  • The prefix namespaces IDs to the origin system, preventing collision with IDs from other systems.
  • The natural ID (SKU, UUID, etc.) is preserved inside the string for debugging and database lookup.

Zalando's UBFF uses the pattern entity:<typename-lowercase>:<natural-id> — for example entity:product:1234 for a Product with SKU 1234 (Source: sources/2023-10-18-zalando-understanding-graphql-directives-practical-use-cases-at-zalando).

The @resolveEntityId directive as a mechanism

Zalando encodes the convention declaratively with a schema directive:

directive @resolveEntityId(
  "An optional override name for the entity name in its ID"
  override: String
) on OBJECT

type Product implements Entity @resolveEntityId {
  id: ID!
}

The directive drives two implementation layers:

  1. Build-time code generation. A TypeScript codegen step walks the schema, finds every @resolveEntityId- marked type, and emits the boilerplate: the id resolver, the __typename resolver, the ID-type definitions, the entity-lookup dispatcher. Engineers don't write the boilerplate; they annotate the type.
  2. Runtime ID wrapping. At resolver-execution time, the id resolver wraps the natural ID (the SKU) with the prefix. The data layer stores 1234; the API layer returns entity:product:1234.

The optional override: String argument lets a typename-rename coexist with ID stability. If Product is renamed to Article but the external ID must remain entity:product:1234 to stay stable for consumers, the directive supports @resolveEntityId(override: "product").

Why type-self-identifying IDs are valuable

  • Debugging. Pasting entity:product:1234 into a log line or ticket immediately communicates the referenced entity kind; a bare 1234 requires the reader to infer context.
  • Generic entity resolvers. A generic lookup(id: ID!): Entity query can dispatch to the correct resolver without a separate type hint — it parses the middle segment and looks up the resolver registry.
  • Cross-surface consistency. The same ID can travel through URLs, logs, tickets, analytics events, and external partner systems without losing its type context.
  • Entity-based page composition integration. Zalando's UBFF composes pages out of entities — Product, Campaign, Brand — per the entity-based page composition discipline. A consistent ID format is the substrate that entity graphs traverse.

The double-click-selectable property

The wiki's concepts/double-click-selectable-identifier concept observes that identifiers where every character is part of the same token (alphanumeric + limited punctuation) double-click-select cleanly in editors and logs. The entity:product:1234 format — with colons that most editors treat as word separators — may not double-click-select as a single token; instead, users triple-click-select the whole line. Organisations choosing an entity-ID convention often pick delimiters based on their editor-ergonomics preference; Zalando's colon convention matches the more general JavaScript / URN conventions (e.g. urn:aws:s3:::bucket, stripe:cus_ABC123) and accepts the not-double-click-selectable trade-off.

Contrast with structure-less IDs

Alternatives that this pattern explicitly rejects:

  • Opaque UUIDs. 550e8400-e29b-41d4-a716-446655440000 carries no type context; consumers must tag types separately.
  • Auto-increment integers. 1234 alone is type-ambiguous across the schema — the same integer can be a Product, an Order, or a Customer.
  • Typed but non-prefixed IDs. productId as a field name carries type context in the field name, not in the value. When the value travels (log lines, event payloads), the type context is lost.

Seen in

Last updated · 501 distilled / 1,218 read