Layers: Domain, Data and Presentation
Separating business logic from data access and UI in a Swift app.
Layers: Domain, Data and Presentation is a free Swift Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Clean Architecture in Swift
Clean Architecture separates code into layers: Domain (business logic), Data (persistence/network), and Presentation (UI).
// Domain: pure Swift, no framework imports
// Data: Core Data / URLSession / JSON
// Presentation: SwiftUI / UIKitThe Domain Layer
The Domain layer contains business entities, use cases, and repository protocols. It has no dependencies on UIKit, SwiftUI, or databases.
// Domain/Entities/User.swift
struct User {
let id: Int
var name: String
var email: String
}
// No import UIKit, no import SwiftDataUse Cases in the Domain
A use case encapsulates one specific business operation. It depends only on repository protocols from the domain.
// Domain/UseCases/FetchUserUseCase.swift
struct FetchUserUseCase {
let repo: UserRepository
func execute(id: Int) async throws -> User {
try await repo.getUser(id: id)
}
}Repository Protocols in the Domain
Repository protocols define the data access contract. The domain owns the protocol; the data layer provides the implementation.
// Domain/Repositories/UserRepository.swift
protocol UserRepository {
func getUser(id: Int) async throws -> User
func saveUser(_ user: User) async throws
}The Data Layer
The Data layer implements domain protocols using concrete technologies (URLSession, Core Data, SwiftData, Keychain).
// Data/Repositories/RemoteUserRepository.swift
struct RemoteUserRepository: UserRepository {
func getUser(id: Int) async throws -> User {
let data = try await URLSession.shared.data(from: userURL(id)).0
return try JSONDecoder().decode(User.self, from: data)
}
func saveUser(_ user: User) async throws { /* PUT request */ }
}Data Transfer Objects
DTOs are Codable types used in the data layer to decode API responses, then mapped to domain entities.
struct UserDTO: Decodable {
var user_id: Int
var full_name: String
func toDomain() -> User { User(id: user_id, name: full_name, email: "") }
}The Presentation Layer
The Presentation layer contains ViewModels and SwiftUI views. It calls use cases and exposes observable state.
// Presentation/ViewModels/ProfileViewModel.swift
@MainActor
final class ProfileViewModel: ObservableObject {
@Published var user: User?
private let useCase: FetchUserUseCase
init(useCase: FetchUserUseCase) { self.useCase = useCase }
func load(id: Int) async {
user = try? await useCase.execute(id: id)
}
}Dependency Direction
Dependencies point inward: Presentation depends on Domain; Data depends on Domain. Domain depends on nothing.
// Outer → Inner dependency rule:
Presentation → Domain ← Data
// Domain is the stable core; outer layers are swappableFolder Structure
Organise Swift files by layer then by feature for large apps.
// MyApp/
// ├── Domain/
// │ ├── Entities/
// │ ├── UseCases/
// │ └── Repositories/ (protocols)
// ├── Data/
// │ └── Repositories/ (implementations)
// └── Presentation/
// ├── Views/
// └── ViewModels/Benefits
Layered architecture enables independent testing, swappable implementations, and parallel team development.
// Test Domain in isolation: no network, no DB
// Swap RemoteUserRepo with LocalUserRepo for offline mode
// Teams own layers independentlyCommon Mistakes
Avoid importing UIKit/SwiftUI in the Domain layer, and avoid business logic leaking into views.
// WRONG: Domain entity with SwiftUI Color
struct Product {
var color: Color // UIKit/SwiftUI import in Domain!
}
// CORRECT: Use a plain string or enum
struct Product { var colorHex: String }Quick Check
Which layer owns the repository protocols in Clean Architecture?
Lesson Recap
Clean Architecture layers: Domain (entities, use cases, protocol contracts), Data (network/DB implementations), Presentation (UI + ViewModels). Dependencies point inward toward Domain. Domain has zero framework dependencies, making it fully unit-testable.
Frequently asked questions
Is the “Layers: Domain, Data and Presentation” lesson free?
Yes — the full text of “Layers: Domain, Data and Presentation” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “Layers: Domain, Data and Presentation”?
Separating business logic from data access and UI in a Swift app. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Layers: Domain, Data and Presentation” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Layers: Domain, Data and Presentation
- Use Case and Repository Patterns
- Dependency Inversion with Protocol Abstractions
- Wiring Layers Together without a DI Framework