04
Product
16
Backend
09
Auth
12
iOS
07
Infra
02
Real-Time

Use newtype IDs to prevent identifier confusion

ADR-0045 ACCEPTED · 2026-03-22
Use Newtype IDs to Prevent Identifier Confusion

Context

The codebase uses UUIDs as identifiers for many domain entities: trips, users, snapshots, modifications, invitations, accommodations, reservations, documents, journey legs, and destinations. When these are all typed as Uuid, the compiler cannot catch cases where a trip ID is accidentally passed where a user ID is expected, or a reservation ID where an accommodation ID belongs.

This class of bug is particularly dangerous in an event-sourced system where Effects carry multiple ID fields — a transposition in the wrong direction silently corrupts trip state.

Decision

Wrap every domain identifier in a dedicated newtype that is not interchangeable with other ID types or raw Uuid.

Each newtype provides:

  • new() — generates a fresh v4 UUID
  • Deref<Target = Uuid> — allows *id for sqlx binds and library interop
  • From<Uuid> — for constructing from database reads
  • serde(transparent) — backward-compatible serialization
  • async_graphql::Scalar — exposed as a custom GraphQL scalar

A uuid_newtype! macro generates these impls to keep boilerplate to one line per type.

Current newtypes

TripId, UserId, VersionId, ModificationId, InvitationId, AccommodationId, ReservationId, DocumentId, LegId, DestinationId

Boundary rules

  • Database reads: wrap with TypeId::from(row.id)
  • Database writes: deref with *id for sqlx bind parameters
  • GraphQL: newtypes are custom scalars, parsed/serialized automatically
  • External libraries (webauthn-rs, etc.): deref at the call site
  • Effect enum: uses newtypes directly — no bridge conversions needed

Consequences

Positive

  • Compiler catches ID mixups at every function boundary
  • Effect application code is clearer — no TypeId::from(*uuid) bridge conversions
  • GraphQL schema communicates intent (tripId: TripId! vs tripId: UUID!)
  • Aligns with ADR-0043 (parse don't validate) — encoding identity invariants in types

Negative

  • Deref (*id) required at database and library boundaries
  • New domain entities require adding a newtype and updating the auth_requirements test's dummy value generator

Related Decisions

  • ADR-0043: Parse Don't Validate — newtypes are a specific application of this pattern for identifiers
  • ADR-0009: Domain-Driven Design — newtypes encode domain identity as part of the ubiquitous language