[새싹 iOS] 24주차_Clean Architecture 적용기

임승섭·2023년 12월 24일
2

새싹 iOS

목록 보기
40/45

Sooda


1. 개요

1. 선택 이유

  • MVVM 적용 후, 확실히 VC의 역할이 많이 줄어들었으나 여전히 VM이 너무 많은 역할을 수행하고 있었다. 레이어 분리를 통해 가능한 한 VM의 역할을 더 줄여보고자 했다.
  • 계획 상 test를 진행해보려고 했기 때문에, test에 용이하다는 코드를 적용시키고자 했다.

2. 진행 과정

  • 구글링해서 정말 많은 레퍼런스를 읽어보았지만,, 솔직히 글만 읽었을 때 직관적으로 이게 뭔지 잘 이해하기 쉽지 않았다
  • 그래서 제일 많이 등장한 프로젝트인 iOS-Clean-Architecture-MVVM를 클론하면서 레이어별로 어떤 코드를 작성했는지 보고, 어떤 방식으로 레이어가 연결되는지 공부했다. 확실히 코드를 먼저 작성해보고 다시 레퍼런스를 읽어보니까 눈에 좀 보이기 시작했다.
  • 어느 정도 구조를 익힌 후, 바로 프로젝트를 생성하고 Clean Architecture를 도입했다.
    • 머릿속에 그림이 도저히 그려지지 않을 땐, 직접 굿노트에 써가면서 구조를 익혔다

3. 글 작성 방향

  • 블로그에 정리를 한다면 아키텍처 내용에 대한 글을 적어야 하나 생각했는데, 그러기에는 이미 너무 좋은 글들이 많다. 굳이 또 같은 그림을 넣고, 똑같은 설명을 해야 할까 싶었다. 그리고 사실,, 추상적인 글을 보고 나조차도 제대로 이해하지 못했기 때문에,,,

  • 그래서 아키텍처에 대한 설명보다는 내가 어떻게 적용했는지에 대해 정리해보려고 한다. 아무래도 사람마다 기준이 다르기 때문에, 내가 이해하고 적용한 방법이 좋은 방향이 아닐 수도 있다. 몇 년 후의 나는 또 다른 구조를 생각하고 있을 수도 있다. 어쨌든, "클린 아키텍처에 대한 내용" 보다는 "내가 적용한 클린 아키텍처"에 대해 작성해보려고 한다.


2. 내가 적용한 클린 아키텍처

아키텍처 구조


폴더링

Sooda
├── Util
│   ├── Enum
│   ├── Constant
│   └── Extension
│
├── Resource
│   ├── Assets
│   ├── Font
│   └── Base
│
├── Domain
│   ├── Entity
│   ├── UseCase
│   └── Interface
│   
├── Presentation
│   ├── CustomView
│   ├── Scene #1
│   │   ├── View
│   │   ├── ViewModel
│   │   ├── ViewController
│   │   └── Coordinator...
│   └── Scene #n
│       ├── View
│       ├── ViewModel
│       ├── ViewController
│       └── Coordinator
│
├── Data
│   ├── UserDefaults
│   ├── Keychain
│   ├── Network
│   │   └── DataMapping
│   ├── Socket
│   │   └── DataMapping
│   ├── Realm
│   │   └── DataMapping
│   └── Repository
│
├── Network
│   ├── NetworkManager
│   └── NetworkRouter
│
├── Socket
│   ├── SocketManager
│   └── SocketRouter
│
├── Realm
│   └── RealmManager
│
└── Application
    ├── AppDelegate
    ├── SceneDelegate
    └── AppCoordinator

1. 중점적으로 생각한 부분

인스턴스 별 역할 분리

  • 아키텍처를 도입한 이유이기 때문에 가장 중점적으로 고려하였다. 기존 VM의 역할을 최대한 분배하고, UseCase와 Repository도 적절한 역한 분리가 이루어지도록 했다.

의존성 방향

  • 레이어 별 의존성의 방향을 유지시켜서 외부 레이어에 수정이 발생하더라도 내부 레이어에 영향이 없도록 했다.

2. 레이어 별 기능

1. Presentation Layer

  • 실제 화면에 가장 가까운(?) 영역이다. MVVM-C 패턴으로 구현하였다.
  • View는 뷰 객체, VC는 사용자 interaction, Coordinator는 화면 전환 로직을 수행한다
  • 다른 레이어와 연결되는 부분은 VM이고, 최대한 불필요한 가공 없이 VC에게 필요한 데이터와 로직을 제공한다.

2. Domain Layer

  • 프로젝트에 가장 필수적인 요소가 있는 영역이다.
  • 가장 내부에 위치한 계층으로, 본인들이 어떻게 활용되는지 알 수 없다.
  • Entity는 프로젝트에서 사용하는 모델 이다.
    모든 곳에서 접근이 가능한 구조체들을 모아두었다.
    • Presentation Layer에서는 이 모델을 바로 뷰 객체에 보여주는 용도로 사용
    • Data Layer에서는 Entity 변형시켜서 요청 모델을 생성, 외부에 데이터를 요청하고,
      응답이 오면 응답 모델을 Entity로 변형시킨다 (DataMapping)

      Enterprise wide business rules, "가장 고수준의 규칙"

  • UseCase는 비즈니스 로직 이다.
    Repository (Data Layer)에서 전달하는 로직을 조합하고, 구조를 변형시켜서 VM (Presentation Layer)로 전달한다.
  • Interface는 Repository를 UseCase에서 사용하기 위한 프로토콜이다.
    실제 Repo들은 Interface를 이용한 의존성 주입을 통해 UseCase에서 활용된다 (DIP)

3. Data Layer

  • Repository에서 외부 통신(?)을 통해 날 것의 데이터(?)를 받는다
  • HTTP API, Socket, DB, Push Notification에서 제공하는 데이터를 받아서 DTO 타입 으로 저장한다.
  • 결국 이 데이터를 UseCase에게 전달해주어야 하기 때문에 DataMapping을 통해 Entity 타입으로 변환시킨다.

3. 트러블

1. 여전히 뚱뚱한 VM

  • 역할에 대한 명확한 기준 없이 무작정 코드로 구현만 하다보니, 자연스럽게 기존 방법대로 구현하면서 여전히 뚱뚱한 VM을 볼 수 있었다.
  • 사실상 Repository는 네트워크 통신 응답만 하고, UseCase는 추가 가공 없이 그걸 그대로 VM에게 전달만 하고 있었다. 결국 나머지 모든 일을 VM에서 해주어야 했다.
  • 이 사태를 인지하고 인스턴스 별 명확한 기준 이 필요하다고 생각했다.

2. 여전히 중복되는 코드

  • 클린 아키텍처를 통해 레이어를 분리하여 역할을 나누면 중복된 코드를 줄일 수 있다.
  • 예를 들어 같은 API 통신 또는 realm CRUD 작업이 여러 화면에서 필요한 경우, 기존에는 모든 VM에서 같은 코드를 작성해야 했다.
    물론 Singleton Manager를 통해 어느 정도 중복된 코드를 줄일 수는 있지만, 추가 가공이 필요한 경우 결국 같은 코드를 작성해야 한다.
  • 이러한 코드들(비즈니스 로직)을 UseCase와 Repository를 통해 캡슐화하고 재사용할 수 있도록 한다.
  • 원래 이렇게 되어야 하는데, 이 역시 명확한 기준이 없다보니 또 여기저기 같은 코드를 남발하고 있었다.

3. 나름대로 해결 방안

  • 결국 내가 해야 할 건 인스턴스 별 명확한 기준 세우기 였다
  • 분리 기준은 오로지 "VM, UseCase, Repo의 역할 분리"와 "코드 중복 최소화"에 집중하면서 세웠다
  • 사실 test에 적합한 기준인지도 확인을 했어야 하는데, 이번 프로젝트에서 test를 진행하지 못했기 때문에 여기에 대해서는 확인하지 못했다.
  1. Repository
    • 외부 데이터(날 것의 데이터) 를 DTO 타입으로 디코딩
    • DTO 타입의 데이터를 Entity 타입으로 Mapping해서 전달
  1. UseCase
    • Repo 데이터를 VM이 사용하기 편하도록 가공해서 전달
    • 최대한 VM이 바로 활용할 수 있도록 가공한다
    • 여러 Repo 데이터를 조합, 타입 변환 등
  1. ViewModel
    • UseCase 데이터를 불필요한 가공 없이 활용
    • VC와의 interaction에 집중

3. 후기

1. 장점

1. VM의 역할 분리

  • 결과적으로는 VM이 확실히 날씬해졌다. Presentation Layer에 필요한 내용만 VM에서 확인할 수 있기 때문에 가독성도 좋아졌다

2. DTO의 장점

  • DTO 사용을 통해 사용 위치에 따라 필요한 데이터만 가지고 있을 수 있게 되었다.
  • 기존에는 네트워크 응답으로 받은 데이터와 DB에서 꺼내온 데이터를 그대로 View에서도 사용했기 때문에 불필요한 데이터를 가지고 있는 경우도 있었고, 분명 같은 뷰에 나타나야 하는 데이터인데 타입이 다른 경우 등 여기저기서 많이 꼬이기도 했다.
  • 이제는 확실히 구조체 자체를 사용 위치에 따라 분리해두었기 때문에 사용하기도 편하고, 가독성 역시 좋아졌다.
  • inittoDomain 을 통해 Entity와 상호작용하기 때문에 사용하기에도 크게 어렵지 않았다.

2. 단점

1. 작업량 증가

  • 어쩔 수 없이 작업량 자체는 많아진다. 단순한 뷰를 만들려고 해도 생성해야 하는 파일 개수가 몇 배로 늘어난다.
  • 그래서 미리 구조를 잘 생각해두고 구현을 시작해야 한다. 중간에 빠진 내용을 추가하려면 여러 파일을 돌면서 연결된 모든 레이어를 수정해야 하기 때문에 쉽지 않다.

2. 역할 분리에 대한 명확한 기준이 없다면,, 클린하지 않은 구조...

  • 처음에 역할 분리에 대해 명확한 기준이 없이 무작정 구현만 했을 때, 오히려 파일만 많아지고 코드를 보기가 더 힘들었다. 이게 클린한게 맞을까..? 싶은 의문이 많이 들었던 시기다.

3. 의존성 방향이 정답인지에 대한 의문

  • 클린 아키텍처의 장점은 의존성 방향이 고정되어 있기 때문에 외부 레이어에 수정이 생기더라도 내부 레이어에는 영향이 없다는 것이다. 추가적인 수정을 하지 않아도 되기 때문에 이는 확실한 장점이 된다
  • 하지만 내부 레이어에 수정이 자주 생기는 프로젝트라면...? 아직 실무에 나가보지 않았기 때문에 경험하지는 못했지만, 만약 Entity를 자주 수정한다면 결국 모든 레이어를 또 수정해야 한다. 이런 경우, 클린 아키텍처가 과연 좋은 아키텍처가 될 수 있을까 하는 의문이 생겼다.

3. 아쉬운 점

1. 테스트 부재

  • 시간 관계상 테스트를 진행해보지 못한 점이 가장 아쉽다. 추후에 시간이 된다면 테스트를 진행해보고, 내가 세운 기준이 테스트에도 적합한지 확인해보려고 한다.

2. ViewModel 프로토콜 부재

  • UseCase와 Repository는 인터페이스, 즉 프로토콜을 이용한 의존성 주입을 통해 활용된다.
  • ViewModel도 역시 이와 동일하게 구현하려 했으나, 기존에 사용하던 Input / Output 방식도 프로토콜로 구현하고 있어서 여기서 발생하는 충돌 때문에 직접 구현체로 사용할 수 밖에 없었다.
  • 분명 같이 사용할 수 있는 방법은 있을 것이기 때문에, 추후 코드를 수정해볼 생각이다.

추가할 내용이 있으면 계속해서 수정해 나가겠습니다. 감사합니다.

0개의 댓글