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

Use Apollo-generated types directly in iOS

ADR-0044 ACCEPTED · 2026-03-18
Use Apollo-Generated Types Directly in iOS

Context

Apollo iOS generates strongly-typed Swift structs from GraphQL fragments and operations. Early in the project, we maintained a parallel layer of domain model types (Trip, Journey, Reservation, etc.) that mirrored these generated types, with init(from:) initializers to convert between them.

This mapping layer was originally motivated by Apollo 1.x not conforming to Sendable, making the generated types unsafe to pass across concurrency boundaries. Apollo 2.0 (adopted in #52) resolved this — all generated types now conform to Sendable.

The mapping layer was removed in #163, eliminating ~800 lines of duplicate type definitions and conversion code.

Decision

Use Apollo-generated fragment types directly throughout the iOS app — in services, views, and helpers. Do not create parallel domain model types that mirror GraphQL types.

GraphQL fragments are the model layer. TripFields and its nested types (TripFields.Destination, TripFields.Journey, etc.) are the view models. They are already strongly typed, Sendable, and kept in sync with the schema by codegen.

Extensions over wrappers. Computed properties and display logic belong in extensions on generated types (e.g., TripFields+Extensions.swift), not in parallel structs with mapping initializers.

Single source per view. A view should get its data from exactly one place: either an @Environment service (for live cache data) or an explicit parameter (for snapshot/historical data that isn't in the cache). A view that takes let trip: TripFields as a parameter and also reads @Environment(TripService.self) has two sources for the same data — that's a smell.

Consequences

  • Schema changes require updating only the GraphQL fragment and any extension files — no parallel type to update
  • Views and services are thinner with less conversion boilerplate
  • @_spi(Unsafe) import ApolloAPI / DataDict construction for previews should be avoided; prefer enabling selectionSetInitializers in the codegen config to get type-safe initializers on fragment types
  • The JourneyEndpoint enum is retained as a genuine domain concept (it models a union type with behavior) — not every Swift type in the iOS layer is a mapping shim