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 UUIDDeref<Target = Uuid>— allows*idfor sqlx binds and library interopFrom<Uuid>— for constructing from database readsserde(transparent)— backward-compatible serializationasync_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
*idfor 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!vstripId: 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