Clean Architecture
1. Layers
┌─────────────────────────────────────┐
│ Presentation │ Views, ViewModels
├─────────────────────────────────────┤
│ Domain │ Use Cases, Entities
├─────────────────────────────────────┤
│ Data │ Repositories, API
└─────────────────────────────────────┘2. Domain Layer
// Entity
struct User: Identifiable {
let id: Int
let name: String
let email: String
}
// Repository Protocol
protocol UserRepository {
func getUsers() async throws -> [User]
func getUser(id: Int) async throws -> User
}
// Use Case
class GetUsersUseCase {
private let repository: UserRepository
init(repository: UserRepository) {
self.repository = repository
}
func execute() async throws -> [User] {
try await repository.getUsers()
}
}3. Data Layer
class UserRepositoryImpl: UserRepository {
private let apiClient: APIClient
func getUsers() async throws -> [User] {
let response: [UserDTO] = try await apiClient.get("/users")
return response.map { $0.toDomain() }
}
}
struct UserDTO: Codable {
let id: Int
let name: String
let email: String
func toDomain() -> User {
User(id: id, name: name, email: email)
}
}4. Presentation Layer
@MainActor
class UserListViewModel: ObservableObject {
private let getUsersUseCase: GetUsersUseCase
@Published var users: [User] = []
init(getUsersUseCase: GetUsersUseCase) {
self.getUsersUseCase = getUsersUseCase
}
func loadUsers() async {
users = try await getUsersUseCase.execute()
}
}📝 Benefits
- Clear boundaries between layers
- Easy to test each layer
- Scalable for large apps
- Independent of frameworks
Last updated on