Serve SwiftUI views from @Observable environment services over Apollo's normalized cache
Decision
Keep SwiftUI views thin and put the rest in @Observable services injected through the SwiftUI environment. A view calls a service when it appears or when the user acts, and reads the result from the service's properties. The services own fetching, the choice between network and cache, offline coordination and business logic, along with the cache's lifecycle.
Treat Apollo iOS's normalized cache as the source of truth for fetched data. A view never holds a copy in @State or receives the data as a parameter, because that copy goes stale the moment the cache updates. A view that shows historical data absent from the live cache is the exception and takes it as a parameter. Each view queries exactly what it shows (ADR-0031). A service refetches the affected watched query after any mutation whose response gives the cache nothing to merge, and clears the cache on logout.
Why
We came to SwiftUI from React Query and wanted its shape: a view declares the data it needs and a cache supplies it, with no global store. Apollo iOS's normalized cache with query watchers gives that shape. SwiftUI's environment already does dependency injection, so the services need no container of their own.
Many of our mutations return a Boolean or an id. A response like that updates no cached record, so the watchers stay stale until the service refetches.
Rejected alternatives
- MVVM with a view model per view. It adds a layer that duplicates what environment services and the cache already provide.
- A Redux-style global store. It adds machinery that SwiftUI's environment already provides and competes with the cache over which copy of the data is current.
Consequences
A cache write reaches every view watching that record, and offline reads build on the cache's SQLite persistence (ADR-0015). Services can be tested on their own. Engineers have to understand how Apollo normalizes and remember the refetch after mutations. Cache keys need configuring so two users' data cannot collide. Services are one layer more than plain MV.