회고
Clean Architecture는 어렵다. 하나를 이해한 것 같아도, 다른 부분을 공부하고나면 이를 연결 짓는 과정이 매우 쉽지않다. 모든 내용이 어려운 것은 아니지만, 정확한 흐름과 이를 구현하기 위해 설계해야하는 구상이 크게 떠오르지 않는다. 다른 개발자들이 구현한 코드를 훑어보고 이를 나름대로 정리하며 계속 익히다보면 보다 이해할 수 있지 않을까 생각한다.
Clean Architecture
어제, 의존성 역전과 3-Layer에 대해 정리했다. 오늘은 각 Layer의 구성요소들들에 대해 정리하고 헷갈릴 수 있는 내용들을 분리해보겠다.
UseCase?
UseCase란 비즈니스 로직 단위이다.
- 정의: 사용자가 수행할 수 있는 하나의 행동
- "검색어를 입력받으면 서버(Repository)에 물어보고, 결과를 최근 검색어 목록에 저장하라"와 같은 흐름제어
- ViewModel이 Massive해지지 않기 위해 로직을 분리한다.
ViewModel의 책임은 무엇인가?
데이터 가공은 비즈니스 로직과 같지않다.
- 비즈니스 로직(Domain): "상품에 할인을 적용하라"와 같은 핵심 규칙
- 데이터 가공(Presentation): "가격에 $를 붙이고, 노란색으로 표시하라"와 같은 화면에 보여주기 위한 준비
즉, ViewModel이 Massive해지지 않기 위해, UseCase로 비즈니스 로직을 분리하고, ViewModel은 데이터의 가공을 맡게된다.
자세한 내용은 아래와 같다.
UseCase vs ViewModel
-
1. UseCase: 비즈니스 로직
- 데이터를 어떻게 다룰 것인가 ?
- 데이터 필터링: 서버에서 받은 국가 중 A를 찾는다.
- 유효성 검사: 환율 값이 0 이하로 들어오면 Error 처리하라.
- 비즈니스 연산: 어제, 오늘 환율을 비교해서 변동 폭을 계산하라.
-
2. ViewModel: 프레젠테이션 로직
- 데이터를 화면에 어떻게 보여줄 것인가?
- 포맷팅: '$'를 추가하라
- 상태관리: 로딩 중에는 로딩 인디케이터를 표시해라.
- 색상결정: 조건에 따라 색상을 다르게 표시해라.
-
3. 정리
- UseCase: 핵심 규칙이 무엇인가?
- ViewModel: 규칙의 결과를 어떻게 보여줄 것인가?
Entity vs DTO
- 1. Entity: 서버와 상관없이 앱 내부에서 사용하기 편한 형태로 정의된 모델
- 2. DTO: 서버 API 응답 형태와 100% 일치해야하는 서버의 언어
- 3. 정리: 쉽게 생각해서 Codable, CodingKeys가 존재하면 서버로부터 받아오는 Data이다. 즉, DTO이며 해당 DTO를 이용하여 앱에서 직접 사용할 Data는 Entity이다.
Repository Interface (Protocol)
마지막으로, 이 기능은 Protocol로 추상화를 진행해야 하는지, 그냥 클래스에서 사용해도 될지를 판단해야 한다. 이 부분이 가장 이해하기 어렵고, 적용도 쉽지 않다고 생각되지만 이해한 부분들에 대해 정리를 해보겠다.
1. 이 기능의 방법이 바뀔 가능성이 있는가?
기능의 결과는 같지만, 해당 기능을 수행해야하는 수단이 여러개가 존재한다면 Protocol로 구현해야 한다.
- Protocol이 필요한 경우: 데이터 저장
- 현재는 서버에 저장하지만, 나중에는 로컬 DB에 저장해야 할 수도있고, 메모리에 임시 저장훌 수도 있다. 즉, 저장에 대한 기능은 같지만 저장을 수행하는 방법이 변할 수 있으므로 Protocol로 구현한다.
- Protocol이 불필요한 경우: UI 컴포넌트의 기본적인 로직
- 특정 UI에 대해서 클릭이 되거나, 단순히 색이 변경되는 로직은 방법이 변하지 않는다.
2. 이 기능이 외부와 연결되는가?
- Protocol이 필요한 경우: 네트워크 통신, 블루투스 제어, GPS, 현재 시간
- 위와 같은 기능들은 테스트를 위해서라도 Procotol이 필요하다.
- Protocol이 불필요한 경우:
- 입력값만 존재하면 결과가 나오는 순수 함수 성격의 기능들은 Protocol로 구현하지 않아도 된다.
3. 이 기능을 누가 소유하고 있는가?
해당 내용이 가장 중요한 기준이다.
- Protocol이 필요한 경우: UseCase가 시키는 일
- UseCase는 "데이터를 가져오라고 시킬거야(Repository)"라고 선언한다. 해당 내용에 대한 실제 구현은 Data Layer에서 진행되며, Domain Layer의 Repository에서 선언된 Protocol이 존재해야 실제 Data Layer에서 Protocol을 구현할 수 있다.
- Protocol이 불필요한 경우: ViewModel 내부의 private 함수
- ViewModel에서 사용하기 위해 만든 private 함수는 교체의 가능성이 적기 때문에 protocol로 구현할 필요가 없다.
4. 정리.
테스트할 때 테스트용 데이터를 필요로 하거나, Layer 사이를 연결하는 기능인가?
- 그렇다: Protocol로 구현하자
- 아니다: 일반 함수/클래스로 구현하자
UseCase가 Repository(Protocol)에게 시키는 일:
- UseCase는 User 정보를 가져오고 싶다.라고 Protocol만 던진다.
- UseCase는 User에 대한 데이터가 Network에서 제공되는 것인지 Local DB에서 제공되는 것인지 관심이 없어야 하므로, Protocol로 구현해야 한다.
참고
ref) https://medium.com/@hyosing92/ios-cleanarchitecture-mvvm-e1b390b18e83