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. This is lesson 1 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, and your progress syncs across the web and the CoddyKit app. The Swift Academy course includes 4 lessons in total.
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.
Learn Swift with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 122
- Lessons
- 409
Frequently Asked Questions
Is the “Layers: Domain, Data and Presentation” lesson free?
Yes — any 3 lessons of the Swift Academy learning path, including “Layers: Domain, Data and Presentation”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Swift Academy course includes 4 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 4.
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