티스토리 뷰

 

📖 목차

1. Clean Architecture를 도입하게 된 계기
2. Clean Architecture Layer
3. Clean Architecture 의존성 역전

 


1. Clean Architecture를 도입하게 된 계기

 

기존에 작성했던 코드의 문제점은 다음과 같다.

 

1️⃣ View와 ViewModel 상태 관리 문제

 

 

기존에 작성한 코드는 view와 viewModel이 너무 결합되어 있고, viewModel의 역할이 너무 많았다.

 

예를 들어, ViewModel의 역할은 다음과 같았다.

  • View에 보여지는 데이터 관리
  • API 요청 및 응답 처리
  • 비즈니스 로직 수행

여러 책임을 ViewModel에서 처리하다 보니, ViewModel의 코드가 지나치게 복잡해졌다.

특히, 하나의 View에서 여러 개의 ViewModel을 사용하는 경우 더 복잡해져서 코드의 유지보수와 확장이 어려웠다.

 

ViewModel의 상태를 View에서 관리하고, 그 상태를 다시 View에서 처리하다 보니 여러 View에서 동일한 ViewModel을 공유하는 상황이 발생했다. 이로 인해 View와 ViewModel 간의 결합도가 지나치게 높아졌고, 하나의 ViewModel이나 View를 수정하면 다른 View와 ViewModel도 함께 수정해야 하는 문제가 생겼다,,,,

 

 

 

그래서 정리를 했다.

View는 화면 이동이나 팝업 표시 여부와 같은 UI 관련 상태만 관리하고, 실제로 보여지는 모든 데이터 상태는 ViewModel에서 관리하도록 하자!

 

View: 무상태
ViewModel: 상태 관리

 

 

2️⃣ DTO 무분별한 사용

 

 

DTO는 데이터를 전달하기 위한 객체로 불변해야 하는 존재다.

그러나 초기에는 DTO와 Model(Entity)의 개념을 제대로 알지 못해, Model을 사용해야 할 곳에서도 DTO를 사용했다.

그 결과, DTO의 불변성을 깨고 각 필드를 수정하는 로직을 작성하고 말았다.

 

다시 정리해 보자. DTO와 Model의 역할을 명확하게 구분하여 사용해야 한다.

DTO는 API 통신 시 데이터를 송/수신하는 용도로만 사용하고, Model은 상태를 정의하고 관리하는 객체로 활용하자!

DTO: API 데이터 송/수신용 (불변)
Model: 상태 정의

 

 

🔎 그래서 왜 Clean Architecture인가?

 

위에서 언급한 문제들은 모두 결합도와 책임 분산의 문제였다.

이걸 해결하기 위해 Clean Architecture를 적용하고자 한 이유는 다음과 같다.

 

1. 결합도 감소

  • Clean Architecture는 각 컴포넌트의 책임을 명확히 분리하여 결합도를 줄일 수 있다.
  • View와 ViewModel이 독립적으로 변화할 수 있어, 하나의 컴포넌트를 수정해도 다른 컴포넌트에 영향을 미치지 않도록 한다.

 

2. 단일 책임 원칙

  • Clean Architecture는 각 계층이 단일 책임을 가지도록 설계함으로써, 각 컴포넌트가 자신의 역할에 집중할 수 있게 한다.

(여기서 객체지향 5원칙 중 하나인 단일 책임 원칙(SRP)을 다시 본다 ㅎ)

 

3. 상태 관리의 명확화

  • Clean Architecture는 상태 관리를 ViewModel에 위임한다.
  • View는 UI 관련 상태만 관리하고, 비즈니스 로직이나 데이터 상태는 ViewModel에서 처리하도록 하여, 역할을 명확히 분리할 수 있다.

 

4. DTO와 Model의 명확한 구분

  • DTO와 Model의 역할을 혼동하면 데이터의 불변성이 깨지고, 코드가 복잡해질 수 있다.
  • Clean Architecture는 데이터 전송 객체(DTO)와 도메인 모델(Model)의 역할을 명확히 구분하여, 각 객체의 책임을 분명히 하고 불변성을 유지할 수 있도록 한다.

 

다른 아키텍처도 많지만 지금 코드는 설계가 부족하고 명확한 구조가 없기 때문에 아키텍처를 도입해서 구조를 바로 잡고자 했다. Clean Architecture를 적용하면 각 계층이 명확히 분리되므로, 부족했던 코드의 구조를 개선하고 유지보수성과 확장성을 높일 수 있을 것 같다!!

코드를 새로 만드는 것보다 어려운 게 원래 있던 코드 리팩터링 하는 건데,,, ㅎㅎㅎ 해봐야지~~~

 


2. Clean Architecture Layer

 

 

 

https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

 

clean architecture은 3개의 주요 계층으로 구성된다.

 

  1. Domain
  2. Data
  3. Presentation

 

1️⃣ Domain

 

 

비즈니스 로직 및 규칙을 포함하며, 애플리케이션의 핵심기능을 정의한다.

 

위의 디렉터리 구조를 보면 Domain 폴더 안에 Entity, UseCase, Interface가 있는 걸 확인할 수 있다.

 

  • Entity: 사용자가 사용할 데이터 모델
  • UseCase: 특정 작업이나 기능을 수행(Domain 관점 사용자 시나리오)
  • Interface: UseCase에서 사용할 Repository 명세(Domain 계층이 외부와 상호작용하기 위한 인터페이스 정의)

 

 

2️⃣ Data

 

 

데이터 저장소와의 상호작용을 처리하며, 외부 API, 데이터베이스, 파일 시스템 등과 연결된다.

 

  • Repository: 하나 이상의 DataSource와 상호작용 처리
  • DTO: API 통신이나 데이터 전송 시 사용되는 불변 객체
  • DataSource: 실제 데이터 저장하거나 가져오는 곳으로 데이터베이스, 외부 API, 파일 시스템 등이 포함

 

 

 

3️⃣ Presentation

 

 

UI와 관련된 모든 요소를 포함하고, 사용자와의 상호작용을 처리한다.

 

 

  • View: UI 요소
  • ViewModel: View의 상태를 관리하고 사용자 이벤트 처리

 

 


3. Clean Architecture 의존성 역전

 

 

여기서 중요한 점은 의존성 방향이다!!!

 

먼저 "의존한다"가 어떤 의미인지 설명할 수 있을까를 생각해봐야 한다.

A가 B를 의존한다를 그림으로 나타내면 다음과 같다.

 

 

A가 B를 의존한다를 코드로 작성하면 다음과 같다.

class A 코드에 class B가 나오고, 만약 B의 코드가 변경되면 A의 코드도 변경되어야 한다.

 

class B {
    var name: String

    init(name: String) {
        self.name = name
    }

    func printName() {
        print("Name: \(self.name)")
    }
}

class A {
    var b: B

    init(b: B) {
        self.b = b
    }

    func useB() {
        b.printName()
    }
}

 

A가 B를 의존한다 = A의 코드에 B가 나온다 = B가 바뀌면 A도 바뀐다

 

 

즉,  의존하고 있는 대상에 변경이 발생하면 자기 자신도 영향을 받을 수 있다고 정리할 수 있다!!!

 

 

 

✏️ 왜 Domain에 의존해야 할까???

 

 

아래의 그림을 보면 Data와 Presentaion 계층이 Domain 계층을 의존하고 있는 걸 확인할 수 있다.

 

https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

 

만약 의존성 방향을 바꾸면 무슨 문제점이 생길까를 생각해 보자.

아래와 같이 단방향으로 의존 방향을 만들면 Data 계층에서 어떤 상태가 바뀌면 Domain과 Presentation 계층이 바뀌게 된다.

 

Data계층의 상태 변화가 Domain과 Presentation계층까지 영향이 가는 것이다.

이렇게 되면 데이터가 바뀔 때마다 도메인 로직이나 UI에서 처리해줘야 하는 상황이 발생하기 때문에 각 계층의 독립성을 해치고, 유지보수성과 확장성이 어려워지게 된다.

 

각 계층은 특정 책임만을 가져야 한다는 단일 원칙 책임이 깨지게 된다.

 

 

그럼 Data와 Presentation이 어떻게 Domain 계층을 의존하도록 할 수 있을까?

바로 의존성 역전 법칙(DIP)을 적용해 의존성을 역전하면 된다.

 

의존성 역전 법칙: "구체화에 의존하는 것이 아니라 추상화에 의존"해야 한다.

즉, 자신보다 변화하기 쉬운 것에 의존해서는 안 된다는 것이다!!

 

"구체화에 의존하는 것이 아니라 추상화에 의존" 이게 대체 무슨 말일까....??

 

 

✏️ 구체화와 추상화 ??

 

구체화란??

 

  • 실제로 존재하는 구현체, 즉 구체적인 구현을 의미
  • 인터페이스나 추상적인 선언이 아니라, 실제로 특정 동작을 수행하는 코드
  • 변화 가능성이 높은 코드

 

추상화란??

 

  • 구현의 세부사항을 감춘 인터페이스를 의미
  • 변화 가능성이 적고, 특정 기술에 의존하지 않도록 설계

 

 

하나의 예시를 들어보자.

Car라는 프로토콜과 Car 프로토콜을 구현하는 SportsCar 클래스가 있다.

뭐가 추상화고 구체화일까??

 

Car 프로토콜은 인터페이스이므로 추상화

protocol Car {
    func drive()
}

 

SportsCar는 Car 프로토콜을 구현하는 구체화된 클래스

class SportsCar: Car {
    func drive() {
        print("Driving fast!")
    }
}

 

 

이제 추상화와 구체화가 무엇인지 구분할 수 있다 ㅎㅎ

 

하지만 왜 구체화에 의존하는 것이 아니라 추상화에 의존해야 하는 것인가에 대해 의문점이 아직 풀리지 않았다.

 

 

구체화에 의존하는 것이 아니라 왜 추상화에 의존해야 할까??

 

🔴 문제점: 구체화에 의존

 

위의 Car 예시에서 Driver라는 클래스가 SportsCar 구체 클래스를 직접 의존하도록 해봤다.

 

class Driver {
    let car: SportsCar

    init(car: SportsCar) {
        self.car = car
    }

    func drive() {
        car.drive()
    }
}

 

만약 Driver가 SportsCar가 아닌 Truck을 사용하고 싶다고 해보자.

 

class Truck: Car {
    func drive() {
        print("Driving slowly with heavy load.")
    }
}

 

그럼 Truck 클래스를 다시 만들고 Driver를 다음과 같이 수정해야 한다.

 

class Driver {
    let car: Truck

    init(car: Truck) {
        self.car = car
    }
	...
}

 

만약 다른 종류의 차를 계속해서 추가해야 한다면 Driver 클래스는 매번 수정되어야 하기 때문에 유지보수성과 확장성에 좋지 않다.

 

🟢 해결 방법: 추상화에 의존

 

SportsCar나 Truck과 같은 구체 클래스에 의존하는 것이 아닌 추상화에 의존, 즉 Car 프로토콜에 의존하도록 하는 것이다.

 

그럼 다음과 같이 수정된다.

class Driver {
    let car: Car

    init(car: Car) {
        self.car = car
    }

    func drive() {
        car.drive()
    }
}

let sportsCar = SportsCar()
let truck = Truck()

let driver1 = Driver(car: sportsCar)
driver1.drive()  // "Driving fast!"

let driver2 = Driver(car: truck)
driver2.drive()  // "Driving slowly with heavy load."

 

이제 Driver 클래스는 SportsCar나 Truck이라는 구체 클래스에 의존하지 않기 때문에 다른 종류의 차를 쉽게 추가할 수 있다.

추상화에 의존하면 확실히 코드의 유연성과 확장성이 향상된 걸 확인할 수 있다.

 

 

✏️ 어떻게 Domain 영역을 의존하도록 할 수 있나

 

 

자 이제 의존성 역전 법칙에 대해서 이해하는 건 성공했고, Data계층이 어떻게 Domain 계층을 의존할 수 있는지 생각해 보자.

 

의존성 역전 법칙은 추상화에 의존해야 한다고 했다.

그럼 Data 계층은 Domain 계층의 비즈니스 로직을 직접 의존하지 않고, Domain 계층에 정의된 인터페이스를 통해 상호작용해야 한다.

 

  • Domain 계층은 상호작용하는 인터페이스 정의
  • Data 계층은 Domain 계층의 인터페이스를 구현하여 통신

 

결과적으로, 구체적인 구현이 아닌 추상화된 인터페이스를 통해 Data 계층이 Domain 계층에 의존할 수 있도록 한다.

 

https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

 

 


 

기존 프로젝트에서 계속 문제가 발생했던 이유를 정리하고, 어떻게 해결할 수 있을지 고민하다가 아키텍처를 도입하게 되었는데 처음에는 이해하기 어려웠다.. 처음에는 왜 이렇게 부분별로 나눠야 하는지 이해하기 어려웠고, 오히려 더 복잡해지는 게 아닌가 하는 생각이 들었다.

 

하지만 아키텍처를 공부하면서 구조가 탄탄해지면 코드 수정과 확장에 훨씬 쉬워질 것이라는 깨달음을 얻을 수 있었다. 이제 클린 아키텍처를 도입한 이유와 개념에 대해 이해했으니, 다음에는 어떻게 프로젝트에 적용했는지 작성해보려고 한다!!

 


 

참고

 

https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함