반응형

SwiftUI에서 많이 하는 실수 - 도메인 모델과 UI 전용 모델을 구분하지 않는 실수

 

도메인 모델(Domain Model)은 비즈니스 규칙 중심,
UI 모델(View Model / UI DTO)은 화면 표현 중심이어야 합니다.
이 둘을 동일 구조체/클래스로 사용하면,
도메인 로직이 UI 요구사항에 끌려 다니거나, 반대로 UI가 도메인 제약에 막혀 불편해집니다.


1. 문제 원인

  • “모델 하나 만들었으니 그냥 어디서나 쓰면 되겠지”라고 생각
  • 포맷팅된 문자열(예: 3,000원, 2024.01.01)을 그대로 도메인에 넣음
  • 서버 DTO → 도메인 → UI 사이 변환 계층을 만드는 것을 귀찮아함

2. 나타나는 증상

  • 통화/날짜/단위 표기 방식 변경 시, 도메인 전체를 수정해야 함
  • UI 요구로 인해 도메인 모델에 isSelected, cellHeight 같은 필드가 추가됨
  • 테스트나 재사용 관점에서 도메인 모델이 UI에 너무 의존적이 됨

3. 잘못된 코드 예시

struct Card {
    // ❌ UI 포맷 문자열을 도메인에 직접 저장
    let id: UUID
    let title: String
    let balanceText: String  // "3,000원"
    let createdAtText: String // "2024.01.01"
    var isSelected: Bool     // 셀 선택 여부
}
  • 포맷이 바뀌면 도메인 정의 자체가 바뀌어야 합니다.

4. 올바른 코드 예시

4-1. 도메인 모델

struct Card {
    let id: UUID
    let title: String
    let balance: Int         // 원 단위 금액
    let createdAt: Date
}

4-2. UI 전용 ViewModel / DTO

struct CardRowViewModel: Identifiable {
    let id: UUID
    let title: String
    let balanceText: String
    let createdAtText: String
    var isSelected: Bool

    init(card: Card, isSelected: Bool, formatter: NumberFormatter, dateFormatter: DateFormatter) {
        self.id = card.id
        self.title = card.title
        self.balanceText = formatter.string(from: card.balance as NSNumber) ?? "\(card.balance)원"
        self.createdAtText = dateFormatter.string(from: card.createdAt)
        self.isSelected = isSelected
    }
}

View에서는 이 UI 전용 모델을 사용:

struct CardRow: View {
    let viewModel: CardRowViewModel

    var body: some View {
        HStack {
            Text(viewModel.title)
            Spacer()
            Text(viewModel.balanceText)
        }
    }
}

5. 정리 및 팁

  • 도메인 모델에는 순수한 데이터와 비즈니스 규칙만 넣습니다.
  • 화면 표시용 문자열, 색상, 이미지 이름 등은 UI 전용 구조체/뷰모델에서 책임지게 만드는 것이 좋습니다.
  • 이 분리를 해두면
    • 여러 UI(SwiftUI, UIKit, 위젯, watchOS)에서 같은 도메인을 재사용하기 쉽고
    • 다국어/통화/날짜 포맷 변경에도 도메인이 흔들리지 않습니다.
반응형
Posted by 까칠코더
,