-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
tomchoi edited this page Mar 20, 2026
·
1 revision
관련 기술: #Swift6.0 #TypedThrows #ErrorHandling #CleanArchitecture
- 각 레이어는 자신의 에러 타입만 외부로 노출한다
- 하위 레이어의 에러 타입이 상위 레이어로 그대로 노출되지 않는다
- 레이어 경계에서 에러를 변환(
+Mapping.swift)한다 -
generic throws사용 금지 — 항상throws(SpecificError)사용
graph TD
FW["🍎 시스템 프레임워크\n(AVFoundation, CoreData...)"]
SE["ServiceError\n(Infrastructure 에러)"]
RE["RepositoryError\n(Domain 정의)"]
UE["UseCaseError\n(Domain 정의)"]
FW -->|"+Mapping.swift\n(Infrastructure)| SE
SE -->|"+Mapping.swift\n(Data/Repositories)| RE
RE -->|"init(_:)\n(Domain/Errors)| UE
핵심 규칙: 에러는 항상 아래 → 위 방향으로 변환되며, 상위 레이어는 하위 레이어의 에러 타입을 알지 못한다.
| 레이어 | 에러 타입 | 정의 위치 | 변환 위치 |
|---|---|---|---|
| Infrastructure (Service) | XxxServiceError |
Data/Sources/Interfaces/ |
Infrastructure/+Mapping.swift |
| Data (Repository) | XxxRepositoryError |
Domain/Sources/Errors/ |
Data/Repositories/+Mapping.swift |
| Domain (UseCase) | XxxUseCaseError |
Domain/Sources/Errors/ |
Domain/Sources/Errors/+Mapping.swift (init) |
flowchart TD
subgraph Framework["🍎 AVFoundation"]
AE["NSError\n(AVAudioSession.ErrorCode)"]
end
subgraph Infrastructure["Data — Infrastructure"]
M1["AudioRecorderServiceError\n+Mapping.swift"]
ASE["AudioRecorderServiceError\n• alreadyRecording\n• sessionActivationFailed\n• mediaServicesFailed\n• startFailed\n• unknown(Error)"]
M1 --> ASE
end
subgraph Repository["Data — Repositories"]
M2["VoiceRecordStartRepositoryError\n+Mapping.swift"]
RSE["VoiceRecordStartRepositoryError\n• alreadyRecording\n• startFailed\n• cancelled\n• unknown(Error)"]
M2 --> RSE
end
subgraph Domain["Domain — UseCases"]
UE["StartRecordingUseCaseError\n• alreadyRecording\n• startFailed\n• cancelled\n• unknown(Error)"]
end
AE -->|"NSError 코드 패턴 매칭"| M1
ASE -->|"throw → catch"| M2
RSE -->|"init(_ error: XxxRepositoryError)"| UE
① AVFoundation → ServiceError (Infrastructure/Audio/AudioRecorderServiceError+Mapping.swift)
extension AudioRecorderServiceError {
init(_ error: Error) {
let nsError = error as NSError
switch nsError.code {
case AVAudioSession.ErrorCode.insufficientPriority.rawValue:
self = .sessionActivationFailed
case AVAudioSession.ErrorCode.mediaServicesFailed.rawValue:
self = .mediaServicesFailed
default:
self = .unknown(error)
}
}
}② ServiceError → RepositoryError (Data/Repositories/VoiceRecords/VoiceRecordStartRepositoryError+Mapping.swift)
extension VoiceRecordStartRepositoryError {
init(_ error: AudioRecorderServiceError) {
switch error {
case .alreadyRecording: self = .alreadyRecording
case .sessionActivationFailed,
.mediaServicesFailed,
.startFailed: self = .startFailed
case .unknown(let e): self = .unknown(e)
}
}
}③ RepositoryError → UseCaseError (Domain/Sources/Errors/VoiceRecords/UseCases/StartRecordingUseCaseError.swift)
extension StartRecordingUseCaseError {
init(_ error: VoiceRecordStartRepositoryError) {
switch error {
case .alreadyRecording: self = .alreadyRecording
case .startFailed: self = .startFailed
case .cancelled: self = .cancelled
case .unknown(let e): self = .unknown(e)
}
}
}Data/
├── Sources/
│ ├── Infrastructure/
│ │ └── Audio/
│ │ └── AudioRecorderServiceError+Mapping.swift ← 프레임워크 의존 매핑
│ └── Repositories/
│ └── VoiceRecords/
│ └── VoiceRecordStartRepositoryError+Mapping.swift ← Service→Repository 매핑
Domain/
└── Sources/
└── Errors/
└── VoiceRecords/
└── UseCases/
└── StartRecordingUseCaseError.swift ← init(_:)로 Repository→UseCase 매핑 포함
Infrastructure 매핑이 Infrastructure 폴더에 있는 이유: AVFoundation 같은 시스템 프레임워크를 import해야 하기 때문에 프레임워크 독립적이어야 하는 Interfaces 폴더에 둘 수 없다.
public func execute() async throws(StartRecordingUseCaseError) -> AsyncStream<Waveform> {
if Task.isCancelled { throw .cancelled }
do {
return try await repository.startRecording()
} catch {
AppLogger.error(error)
throw StartRecordingUseCaseError(error) // 한 줄 변환
}
}catch 블록은 항상 두 가지만 한다:
-
AppLogger.error(error)— 로깅 -
throw XxxUseCaseError(error)— 변환 후 전파
-
cancelled: 모든 에러 타입에 존재,
errorDescription은nil반환 - unknown(Error): 예측 불가한 에러를 감싸 원본 정보 보존
- 세분화: 상위 레이어로 올라갈수록 케이스가 줄어드는 것이 자연스럽다 (세부 원인을 추상화)
graph LR
SE["ServiceError\n5가지 케이스\n(세분화됨)"]
RE["RepositoryError\n4가지 케이스"]
UE["UseCaseError\n4가지 케이스\n(추상화됨)"]
SE -->|"세부 원인 병합"| RE
RE -->|"1:1 매핑"| UE
- 도메인-레이어-파라미터-설계:-URL-vs-Entity
- 도메인-에러-설계:-유스케이스별-vs-도메인(기능)별
- Swift-6.0-Typed-Throws-Guideline
- CoreData-loadPersistentStores-비동기-처리-전략
- stateless-Infrastructure-서비스의-struct-전환
- STT-Repository-동시-요청-큐잉-전환
- Coordinator-클래스-기반에서-프로토콜-기반으로-마이그레이션