Creates DataSources for data access. Use when creating RemoteDataSource for REST APIs, MemoryDataSource for in-memory storage, LocalDataSource for UserDefaults persistence, or DTOs for API responses.
Guide for creating DataSources following the Repository pattern.
CRITICAL: Do NOT assume the API schema or operations. Ask the user before writing any code:
Only proceed to implementation after the user confirms these details.
| Type | Transport | Contract | Implementation | Error Mapper |
|---|---|---|---|---|
| REST | HTTP | nonisolated protocol: Sendable |
nonisolated struct with HTTPClientContract |
HTTPErrorMapper |
| GraphQL | HTTP/GraphQL | nonisolated protocol: Sendable |
nonisolated struct with GraphQLClientContract |
GraphQLErrorMapper |
| Memory | In-memory | : Actor |
actor with dictionary storage |
ā |
| UserDefaults | Local | : Actor |
actor with UserDefaults |
ā |
Each reference contains full templates: contract, implementation, DTO, mock, fixtures, and tests.
Features/{Feature}/
āāā Sources/
ā āāā Data/
ā āāā DataSources/
ā ā āāā Remote/
ā ā ā āāā {Name}RemoteDataSourceContract.swift
ā ā ā āāā {Name}RESTDataSource.swift (or {Name}GraphQLDataSource.swift)
ā ā āāā Local/
ā ā āāā {Name}LocalDataSourceContract.swift
ā ā āāā {Name}MemoryDataSource.swift
ā ā āāā {Name}UserDefaultsDataSource.swift # Optional: UserDefaults
ā āāā DTOs/
ā āāā {Name}DTO.swift
āāā Tests/
āāā Unit/Data/
ā āāā DataSources/
ā āāā Remote/
ā ā āāā {Name}RESTDataSourceTests.swift (or {Name}GraphQLDataSourceTests.swift)
ā āāā Local/
ā āāā {Name}MemoryDataSourceTests.swift (or {Name}UserDefaultsDataSourceTests.swift)
āāā Shared/Fixtures/
ā āāā {name}.json
ā āāā {name}s_response.json
āāā Shared/Mocks/
nonisolated protocol: Sendable with @concurrent on async methods. Local contracts (Memory, UserDefaults): : Actornonisolated struct with @concurrent on async methodsHTTPClientContract, GraphQLClientContract) also use @concurrent for off-MainActor executionChallengeNetworking uses nonisolated default isolation ā networking types don't need nonisolated annotations@concurrent async throws. Local (Memory, UserDefaults): methods are actor-isolated (implicitly async from caller)Sendable vs Actor contracts: Use
: Actorwhen the DataSource has its own mutable state to protect (Memory, UserDefaults,ImageDiskCacheContract). Use: Sendablewithnonisolatedmethods only for stateless wrappers around thread-safe APIs (e.g.,FileSystemwrappingFileManager). See/concurrencyskill "Actor Reentrancy" section for when and why to choose: Sendable.
"A Data Transfer Object is one of those objects our mothers told us never to write. It's often little more than a bunch of fields and the getters and setters for them." ā Martin Fowler, PoEAA
DTOs are intentionally anemic ā they exist purely to transfer data between systems.
Rules:
Decodable, EquatabletoDomain() methods ā mapping belongs in the RepositoryInt, GraphQL IDs are StringDataSources catch transport errors and map them to APIError:
HTTPError ā APIError via HTTPErrorMapperGraphQLError ā APIError via GraphQLErrorMapperRepositories and upper layers only see APIError, never transport-specific errors.
| Component | Visibility | Location |
|---|---|---|
| Remote Contract | internal | Sources/Data/DataSources/Remote/ |
| Remote Implementation | internal | Sources/Data/DataSources/Remote/ |
| Local Contract | internal | Sources/Data/DataSources/Local/ |
| Local Implementation | internal | Sources/Data/DataSources/Local/ |
| DTO | internal | Sources/Data/DTOs/ |
| Mocks | internal | Tests/Shared/Mocks/ |
Remote/ with async throwsRemote/ with HTTPErrorMapperTests/Shared/Mocks/Tests/Shared/Fixtures/String)Remote/ with async throwsRemote/ with GraphQLErrorMapperTests/Shared/Mocks/Tests/Shared/Fixtures/Local/ with : Actoractor Implementation in Local/actor Mock with setter methods and call trackingawait for all mock reads/writes)Local/ with : Actoractor Implementation in Local/ with private let userDefaultsactor Mock with setter methods and call trackingasync tests using custom UserDefaults suite (nonisolated(unsafe) on test property)