본문 바로가기
iOS/develop log

🚀 머플러 앱 리팩토링 - 4. Domain Layer와 서비스 구조 설계

by 개발하는 감자입니다 2025. 2. 11.
728x90

머플러 앱 리팩토링의 여정을 이어가는 이번 포스팅에서는 Domain Layer 설계서비스 구조를 어떻게 개선했는지 공유합니다. 이전 글에서는 MVVM 전환 과정에서의 시행착오와 뷰 중심의 플로우 차트 작성, 그리고 구조적 개선의 필요성에 대해 다뤘습니다. 이제 그 과정을 구체화하면서, 머플러 앱의 복잡한 로직을 효율적으로 관리하기 위한 핵심 구조를 설명하겠습니다.

 

📌 1. 플로우 차트 작성 후, 도메인 설계의 중요성

플로우 차트를 그린 후, 가장 중요한 부분이 도메인 로직을 먼저 설계하는 것이라는 결론에 도달했습니다.

  • 기존에는 UI와 기능 구현 위주로 개발을 진행했지만, 이번 리팩토링에서는 핵심 비즈니스 로직을 중심으로 설계하는 접근 방식을 채택했습니다.
  • 도메인 설계를 진행하면서 놀라웠던 점은, 생각보다 많은 로직이 나오지 않았다는 것입니다. 처음에는 복잡하고 거대해 보였던 코드들이 실제로는 단순하고, 중복된 부분이 많다는 것을 깨달았습니다.

이러한 경험을 통해, 무작정 구현만 하는 것이 비효율적이라는 점을 다시금 느꼈습니다. 설계를 통한 구조적 이해가 개발 시간을 단축하고, 유지보수성을 높인다는 중요한 교훈을 얻었습니다.

🚀 2. 새로운 접근: Domain Layer와 서비스 구조 설계

복잡성을 해결하기 위해, 단순한 뷰 리팩토링을 넘어서 Domain Layer서비스 구조를 새롭게 설계했습니다.

✅ Domain Layer 설계 목표

  1. 명확한 책임 분리 (Separation of Concerns):
    • UI, 비즈니스 로직, 데이터 처리를 분리하여 유지보수성을 높임
  2. 비즈니스 로직의 중심:
    • 데이터 구조(Entity), 로직(UseCase), 저장소(Repository)를 중심으로 설계
  3. 유연한 확장성:
    • 새로운 기능이 추가될 때 기존 코드에 최소한의 영향만 미치도록 설계

 

📦 3. Domain Layer 구조

Domain
├── Category
│   ├── Entity
│   │   └── Category.swift
│   ├── Repository
│   │   └── CategoryRepository.swift
│   └── Service
│       └── CategoryService.swift
├── Goal
│   ├── Entity
│   │   └── Goal.swift
│   ├── Repository
│   │   └── GoalRepository.swift
│   └── Service
│       └── GoalService.swift
├── Consumption
│   ├── Entity
│   │   └── Consumption.swift
│   ├── Repository
│   │   └── ConsumptionRepository.swift
│   └── Service
│       └── ConsumptionService.swift
└── DailyEvaluation
    ├── Entity
    │   └── DailyEvaluation.swift
    ├── Repository
    │   └── DailyEvaluationRepository.swift
    └── Service
        └── DailyEvaluationService.swift

 

 

🗂️ 4. 핵심 요소 설명

📍 1) Repository

Repository는 데이터 소스(로컬 DB, API 등)와 도메인 레이어를 연결하는 역할을 합니다.

protocol ConsumptionRepository {
    func createConsumption(_ consumption: Consumption)
    func updateConsumption(_ consumption: Consumption)
    func deleteConsumption(by id: String)
    func fetchConsumptions() -> [Consumption]
}
  • 의존성 역전 원칙(DIP) 을 적용하여 구체적인 데이터 저장 방식은 외부에 위임
  • 테스트 환경에서는 Mock Repository로 대체 가능

📍 2) Entity

Entity는 도메인의 핵심 데이터 구조를 정의합니다.

struct Consumption: Identifiable {
    let id: String
    var amount: Double
    var date: Date
    var category: String
    var isRecurring: Bool
    var recurrenceRule: RecurrenceRule?
}
  • 불변성(Immutable) 유지로 데이터 안정성 강화
  • 복잡한 비즈니스 규칙은 Entity 내부 메서드로 캡슐화 가능

📍 3) Service (UseCase)

초기에 유스케이스를 작성하려 했으나, 너무 많은 파일이 생성될 것 같아 Service라는 객체를 도입했습니다. 이를 통해 여러 함수를 하나의 서비스 객체 안에서 관리할 수 있도록 하였습니다.

protocol ConsumptionService {
    func createConsumption(_ consumption: Consumption)
    func updateConsumption(_ consumption: Consumption)
    func deleteConsumption(by id: String)
    func getAllConsumptions(sortOrder: SortOrder) -> [Consumption]
}
  • 비즈니스 로직의 중심으로, 데이터 가공 및 처리 담당
  • ViewModel에서는 Service만 호출하여 로직 처리

아직 이 방식이 최종 확정된 것은 아니지만, 대규모 서비스에서는 이런 식으로 설계하는 경우가 많다고 합니다.


🚀 5. 서비스 구조의 핵심 포인트

✅ 소비 데이터 정렬 기능 (최신순/오래된순)

enum SortOrder {
    case newest
    case oldest
}

func getAllConsumptions(categoryId: String?, sortOrder: SortOrder) -> [Consumption] {
    let consumptions = repository.fetchConsumptions().filter { $0.category == categoryId }
    return sortOrder == .newest ? consumptions.sorted { $0.date > $1.date } 
                                : consumptions.sorted { $0.date < $1.date }
}
  • SortOrder를 통해 다양한 정렬 방식 지원
  • 소비 패턴 분석 기능과의 연결성 강화

✅ 반복 소비 관리

struct RecurrenceRule {
    var frequency: RecurrenceFrequency
    var interval: Int
    var weekdays: [Weekday]?
    var endDate: Date?
}

func getAllRecurringConsumptions() -> [Consumption] {
    return repository.fetchConsumptions().filter { $0.isRecurring }
}
  • 복잡한 반복 소비 패턴(매주 화/목 등) 지원
  • 구독 서비스 관리에도 활용 가능

📊 6. 보고서 기능 (GoalReport, CategoryGoalReport)

📍 CategoryGoalReport

struct CategoryGoalReport {
    let category: Category
    let currentAmount: Double
    let targetAmount: Double
    let averageExpense: Double
    let maxExpense: Double
}
  • 카테고리별 소비 현황 분석
  • 평균 소비, 최대 지출, 달성률 등의 데이터 제공

📍 GoalReport

struct GoalReport {
    let currentAmount: Double
    let targetAmount: Double
    let dailyAverageExpense: Double
    let spendingDays: Int
}
  • 목표 대비 현재 소비 상태 분석
  • 일일 평균 소비량 및 목표 달성률 계산

✅ 7. 마무리하며

이번 리팩토링을 통해 단순한 코드 정리를 넘어서, 머플러 앱의 확장성과 유지보수성을 높이는 구조적 개선을 이루었습니다.

💡 핵심 인사이트

  • 중복 제거: 공통 컴포넌트 정의로 코드 재사용성 향상
  • 명확한 책임 분리: UI와 비즈니스 로직의 독립성 확보
  • 확장성 강화: 새로운 기능 추가 시 최소한의 수정으로 대응 가능

무작정 구현만 하는 것은 효율적이지 않으며, 사전 설계를 통해 개발 시간을 단축하고 품질을 높일 수 있다는 것을 다시 한 번 깨달았습니다.

읽어주셔서 감사합니다! 😊

728x90
반응형