2022. 2. 28. 17:06ㆍProgramming/iOS_Swift
이 글은 약 5주간 진행했던 Service Level Project(이하 'SLP')를 회고 및 정리하기 위한 글이다. 이번 프로젝트를 진행하면서 새로운 시도를 많이 했는데 그 중 가장 큰 도전이었던 MVVM + Clean Architecture(클린 아키텍처) 적용에 대해서 정리하려고 한다.
목차는 다음과 같다. 2편에서는 4번부터 다루겠다.
1. Clean Architecture(클린 아키텍처)란?
2. MVVM + Clean Architecture의 구성요소
3. 전반적인 Flow
4. Dependency(의존성)와 Dependency Inversion(의존성 역전)
5. SLP에 왜 Clean Architecture를 적용하였는지?
6. Clean Architecture 적용 후기 및 회고
< 1편 >
4. Dependency(의존성)와 Dependency Inversion(의존성 역전)
의존성이란?
두 개의 클래스가 있을 때, 두 클래스 간의 관계를 의미한다. 만약 하나의 클래스가 다른 클래스를 어떤 용도로 참조하여 사용할 때 우리는 의존성이 있다라고 말할 수 있다. 간단한 예시를 통해 의존성이 무엇인지 이해해보자.
import Foundation
class General {
var name = "Ethan"
}
class Leader {
var general = General()
var name = "DalTonic"
}
class Trainee {
var leader = Leader()
var name: String
init(name: String) {
self.name = name
}
}
var trainee = Trainee(name: "Park")
'장군'클래스, '리더' 클래스, '훈련병'클래스 총 3개가 있다. 여기서 일반적인 군대를 생각해봤을 때, 장군은 자기 상관이 웬만해서는 없기 때문에 name이라는 저장프로퍼티만 존재한다. 한편, 리더의 경우 자신의 상관이 분명 존재할 것이기 때문에 '장군' 클래스를 참조하고 있다. 마찬가지로 '훈련병'클래스 또한 상관인 리더가 존재하기 때문에 '리더' 클래스를 참조하고 있다.
< 참조 관련 글 참고 >
이럴 때, 우리는 의존성이 있다라고 말할 수 있다. 즉 훈련병 클래스는 리더 클래스에게 의존하고 있고 리더 클래스는 장군 클래스에 의존하고 있다.
의존성이 무엇인지는 이제 대충 알겠다. 그렇다면 이게 어떤 문제를 야기할 수 있는지도 알아야 한다.
다시 군대의 예시로 돌아와서, 만약 여러분이 리더의 포지션에 있는데 여러분의 상관인 장군이 갑자기 바뀐다고 상상해보자. 그러면 정말 골치 아파진다. 바뀐 장군의 성향에 따라서 기존의 모든 지침이나 일정들이 순식간에 바뀔 것이다. 리더의 포지션에 있는 여러분은 바뀐 장군의 명령에 의해 훈련병들을 지휘하는 방식도 달라질 수 있을 것이다.
즉, 의존을 받고 있는 최상단 클래스인 '장군'클래스에 변화가 생긴다면 하위에 있는 '리더' 클래스와 '리더' 클래스를 의존하는 '훈련병'클래스까지 모두 영향을 받게 되는 것이다.
클린 아키텍처로 돌아와보자.
Dependency의 방향을 보면 Presentation Layer와 Data Layer가 Domain Layer에게 의존하고 있음을 확인할 수 있다. 각 레이어의 세부 구성요소에 따라 풀어서 설명하면, View는 ViewModel에게 의존하고 ViewModel은 UseCase에게 의존하고 있다. Data Layer의 Repository또한 UseCase에게 의존하고 있다. UseCase는 그 어떤 것도 의존하지 않는다.
그런데 1편에서 보았던 Data의 flow를 보면 UseCase에서 Repository 쪽으로 향하고 있는 것을 확인할 수 있다. 이를 보고 의존성의 방향이 Repository쪽으로 향하고 있는 것이 아닌가라고 생각할 수 있다. 여기서는 바로 다음 설명하게될 의존성 역전(Dependency Inversion)이 일어나게 되면서 의문이 해결된다.
의존성 역전(Dependency Inversion)
의존성 역전은 단어 뜻 그대로 의존성의 방향이 바뀌게 되는 것이다. 이해를 위해 아까 사용한 군대의 예시로 돌아와보자.
위의 예에서 설명했듯이 장군이 바뀌게 되면 장군을 의존하고 있던 리더가 바뀌고 리더를 의존하던 훈련병도 바뀐다고 했다. 그렇다면 장군이 바뀌더라도 아래에 있는 클래스들이 안바뀔 수 있도록 컨트롤 해주는 중간 계층이 있다면 어떨까. 군대에서 장군에게는 일종의 비서 개념인 전속 부관이 존재한다. 만약 전속 부관이 장군이 해야할 일 리스트를 보유하고 규칙을 잘 정립하고 있다면 장군이 바뀌더라도 변화는 없을 것이다. 즉 리더와 장군 둘다 전속 부관에게 의존하는 구조가 되는 것이다.
그림으로 표현하면 요런 느낌이 될 것 같다. 이제 이를 코드로 구현한다면 다음과 같다.
import Foundation
protocol GeneralInterface: AnyObject {
var name: String { get set }
}
class General: GeneralInterface {
var name = "Ethan"
}
class Leader {
var generalInterface: GeneralInterface
var name = "DalTonic"
init(generalInterface: GeneralInterface) {
self.generalInterface = generalInterface
}
}
class Trainee {
var leader: Leader
var name: String
init(leader: Leader, name: String) {
self.leader = leader
self.name = name
}
}
var trainee = Trainee(leader: Leader(generalInterface: General()), name: "Park")
GeneralInterface라고 하는 프로토콜이 바로 앞서 설명한 장군의 전속 부관 역할을 수행하는 것이다. 인터페이스는 장군이 지켜야할 규칙이나 해야할 일 리스트 등을 알고 있고 '장군'클래스는 해당 프로토콜을 채택하고 있다. 한편 '리더'클래스는 이제 '장군'클래스와 직접적으로 소통할 필요가 없어졌으니 전속 부관인 인터페이스에게만 의존하면 된다.
다시 클린아키텍처로 돌아오자.
1편에서 사용하였던 그림인데, 다시 가져왔다. 레포지토리 인터페이스가 바로 레포지토리의 전속 부관역할을 수행하는 것이다. 즉 레포지토리는 레포지토리 인터페이스에게 의존하고 유즈케이스는 레포지토리 인터페이스에게 의존하므로 데이터 레이어로부터 도메인 레이어로 향하는 의존성 역전이 일어나게 된다.
5. SLP에 왜 Clean Architecture를 적용하였는지?
1) Volume
가장 주된 이유 중의 하나는 이번 프로젝트는 실제 서비스를 할 수 있을 정도의 레벨인 어플리케이션을 구현하는 것이 목표였기에 작업해야할 양이 많았고 기간도 길었다. 구현해야했던 기능을 나열해보면, 전화번호 인증 / 로그인 / 회원가입 / 프로필 / 프로필 업데이트 / 지도에서 주변 친구 찾기 / 취미설정 / 취미가 같은 친구에게 채팅 요청하기 / 채팅 수락 / 실시간 채팅 / 리뷰 / 신고 / 인앱결제 기능 등을 구현해야 했다.
기간이 5주 정도로 길었고 구현해야할 기능이 다양하고 복잡했기 때문에 클린 아키텍처의 장점을 살리는 데에 좋다고 생각했다. 또한 실제 서비스를 한다고 가정하고 만드는 것이었기 때문에 장기적인 관점에서 쉽게 테스트가 가능하고 유지 보수가 용이한 클린 아키텍처가 적합하다고 판단했다.
2) 수시로 변경되는 기획
또 다른 주된 이유는 프로젝트를 시작할 때, 멘토님들이 이번 프로젝트는 실제 현업에서 일어나는 일들을 가정하기 위해 기획과 디자인이 수시로 바뀔 것이라는 정보를 전달하셨기 때문이다. 만약 MVC나 MVVM처럼 뷰모델이나 뷰컨트롤러의 역할이 크고 코드가 모두 유기적으로 연결되어있을 경우, 기획에 변화가 생긴다면 그 변화가 사소할지라도 개발쪽에서는 변화에 대처하기 위한 소요가 무척 클 수 있다.
그러나 클린 아키텍처를 적용한다면 클린 아키텍처의 이점인 계층과 역할의 명확한 분리로 인해 변화에 대한 유연한 대처가 가능하다. 따라서 수시로 변경될 기획을 고려했을 때, 클린 아키텍처가 이번 프로젝트에 적합하다고 판단했다.
3) 학습적 차원
마지막 이유는 학습적 차원이다. 가장 주된 이유이지 않을까 싶다. 큰 규모의 아키텍처를 연구하고 적용해봄으로써 개발 구조에 대해 고민하고 어떤게 좀 더 프로젝트에 적합한 아키텍처일지 학습하기 위해 클린 아키텍처를 이번 프로젝트에 적용시켰다.
6. Clean Architecture 적용 후기 및 회고
클린 아키텍처를 프로젝트에 적용해보고 드는 생각은 결국 다양한 아키텍처 패턴이 존재하지만 어떤 게 더 좋은 아키텍처고 더 나쁜 아키텍처는 아니라는 것이다. MVC를 쓴다고 해서 구식의 아키텍처 패턴을 사용하는 것이 아니고 클린 아키텍처를 사용한다고 해서 결코 더 좋은 아키텍처 패턴을 쓴다고 말할 수 없는 것이다. 오히려 70년대에 소개된 MVC패턴이 지금까지도 대중적으로 사용되고 있다는 것은 그만큼 보편적이고 좋은 아키텍처라는 것을 증명하고 있다. 다만, 우리는 우리가 어떤 프로젝트를 수행하느냐에 따라 적합한 아키텍처는 분명 존재한다고 말할 수 있을 것 같다. 결국 나는 이번 클린 아키텍처 적용을 통해 프로젝트에 따라 적용할 수 있는 아키텍처 패턴 한 가지를 더 알게 된 것일뿐 그 이상도 그 이하도 아니라고 말하고 싶다.
클린 아키텍처를 적용하면서 사실 너무 많은 우여곡절이 있었다. 추후에 포스팅 하게될 Coordinator 패턴과 더불어서 클린 아키텍처는 내 두뇌의 한계를 테스트 하는 느낌이었다. 한글로 된 레퍼런스를 봐도 이해가 가지 않았고 팀원에게 질문을 해봐도 명쾌하게 와닿지 않았다. 스스로의 멍청함에 분노하며 무작정 부딪치기만 한 것 같다. 사실 클린 아키텍처 외에도 SwiftLint, SwiftGen, Moya 등 새롭게 시도해보는 것들도, 관련된 에러들도 많았지만 클린 아키텍처가 핵불닭볶음면이었다면 얘네는 그냥 진라면 순한맛 정도였다. 오죽했으면 프로젝트를 시작하고 2주 정도 지난 시점에 클린 아키텍처를 포기하고 MVVM으로 다시 전환할까 고민했을 정도였으니 말이다.
완벽하지 않더라도 끝까지 붙잡아 결국 적용에 성공하고 이렇게 내가 이해한대로 정리할 수 있다는 것에 감개가 무량하다. 이번 클린 아키텍처 도전을 계기로 추후에 새로운 도전을 할 수 있는 자양분을 얻었다. 그런 점에서 나에게 이번 프로젝트는 남다른 의미를 가진 프로젝트였다.
'Programming > iOS_Swift' 카테고리의 다른 글
[CS] 객체지향 프로그래밍(OOP) 이해하기 (0) | 2022.03.14 |
---|---|
[iOS_CS] COW(Copy On Write) 이해하기 (0) | 2022.03.06 |
[iOS_Service Level Project(SLP)] Clean Architecture 적용기 (1) - MVVM과 Clean Architecture (0) | 2022.02.25 |
[iOS_Swift] Generic(1) - Generic Basic (0) | 2022.02.14 |
[iOS_Swift] inout Parameter에 대하여 (0) | 2022.02.10 |