Repositories, gateways, clients, and ports and adapters
A port states what the application needs from an external capability. An adapter translates that contract to SwiftData, files, URLSession, CloudKit, or a deterministic local implementation.
Name the application purpose
protocol NoteLibrary: Sendable { func notes(matching query: NoteQuery) async throws -> [Note] func note(id: UUID) async throws -> Note? func save(_ note: Note) async throws func delete(id: UUID) async throws}NoteLibrary describes Field Notes. SwiftDataRepositoryProtocol would expose a vendor choice and encourage persistence details to leak into callers.
Make adapters honor one contract
An in-memory adapter should preserve identity, validation, ordering, replacement, and read-after-write behavior. A SwiftData adapter maps persistent models to domain values and translates storage failures into application errors. Contract tests run the same scenarios against both.
Different names signal different responsibilities:
- a repository or library offers collection-like domain access
- a gateway wraps an external business capability
- a client handles a transport protocol such as HTTP
- an adapter translates between an application port and one technology
Names matter less than clear ownership and dependency direction.
Do not protocolize everything
A concrete pure formatter needs no interface just because it is concrete. Add a port when there is a volatile integration, useful local substitute, test distance improvement, or policy boundary. Keep transactions and multi-step consistency in one owner rather than leaking a sequence of primitive calls to every use case.
Series navigation
- Previous: Part 65: Coordinators, routers, deep links, and restoration
- Next: Part 67: Modularization with Swift Package Manager
- Series index: Zero to iOS Hero
References
- Adding and editing persistent data describes SwiftData persistence operations.
- URLSession is Foundation’s HTTP and transfer boundary.