MVVM + Clean Architecture 정리

Zeto·2022년 6월 24일
6

Swift_Architecture

목록 보기
1/2

그동안은 프로젝트를 진행하게 되면 나름 익숙하다고 생각되는 MVC 패턴을 기반으로 작업을 하였고, 이마저도 기능 개발에만 초점을 두다보니 설계나 정리를 비교적 등한시하게 되었다.
이러다보니 점점 앱의 기능이 늘어날 때마다 전반적인 구조가 복잡해지고 본인이 작성했음에도 불구하고 볼 때마다 새로운 느낌을 받는 어려움을 느꼈다.

이와 같이 매번 익숙한 패턴으로 접근하는 습관과 설계와 정리가 부족하여 복잡성이 높아지는 문제를 해결해보고자 이번 프로젝트 때는 MVVM 패턴과 Clean Architecture를 공부하고 이를 적용해보고자 마음 먹게 되었다.

MVVM 패턴과 Clean Architecture

😚 First Step.

먼저 Clean Architecture를 적용하기 전, MVVM 패턴의 흐름이 어떤 것인지를 파악하고 이를 자신의 그림으로 도표화하는 걸 최우선 목표로 두었다.

기본적으로 MVVM 패턴은 Model, View(VC), ViewModel로 이루어져 있고, 이러한 세가지의 구성요소가 상호 연결되어 있는 구조이다.
Model은 데이터를 처리하고, View는 UI를 담당하는데 이 둘은 직접적으로 연계가 되면 안 되기 때문에 이러한 연결을 담당해주는 것이 ViewModel의 역할이다.

이러한 원론적인 개념과 함께 Usecase나 Repository, Entity, DTO를 MVVM과 연관시켜 흐름과 역할을 구성하는 것이 필요하였고 다양한 자료들을 보고, 같이 작업하는 동료와 많은 대화를 나누면서 이를 아래와 같이 정리하였다.

기본 MVVM 흐름에 대한 도표 정리

  • ViewModel(VM)은 View의 Action에 대한 적절한 데이터의 Update를 전달하여 View가 올바르게 갱신될 수 있도록 해주되, 어떠한 비즈니스 로직을 가지고 있어서는 안 된다.

  • VM은 비즈니스 로직에 해당하는 여러 Usecase를 가지고 있으며, 이들을 통해 원하는 데이터의 Update를 위한 작업 흐름을 수행한다.
    (쉽게 얘기하면 기존에 메서드로 수행하던 작업 단위정도의 범위를 Usecase로 사용하는 것.)

  • UseCase는 Repository를 소유(구체 타입 직접 소유X, 약한 결합 필요)하고 있으며, Repository로부터 받아온 DTO를 VM에게 전달할 적절한 Entity를 변환하여 준다.

  • Repository는 EndPoint를 소유하고 있으며, 로컬 DB에 데이터가 존재하는 지 확인하고 필요할 경우 네트워크 통신을 진행하여 로우한 데이터를 받아온다. 해당 데이터를 받으면 DTO로 변환하여 전달한다.

  • 용어 정리 : DTO는 Data타입의 로우한 데이터를 Swift에서 쓸 수 있는 타입으로 바꿔줄 수 있도록 작성된 타입. Entity는 변환된 DTO에서 각 작업 별로 필요한 요소들만 뽑아서 버무린 선별 타입.
    (어떤 곳에서는 DTO를 Entity로, Entity를 Model로 지칭하기도 함.
    DTO의 변환 없이 그대로 사용 가능할 경우에는 DTO 자체를 Entity로 사용해도 무방.
    )

😚 Second Step.

다음으로는 전반적인 흐름에 프로토콜을 적절하게 활용하여 의존성을 덜어내주고, 객체 간의 결합도를 약하게 만들어주는 점에 초점을 두고 기존 도표에 추가 사항을 기입하였다.

기본 MVVM 흐름에 프로토콜 추가

😚 Third Step.

이렇게 전반적인 MVVM 흐름을 나름대로 정리하였고, 이를 토대로 우리의 프로젝트에 대입하여 보고자 했다.
현재 Github와 Apple의 OAuth 로그인 화면에서 버튼을 클릭하고 ResourceServer에서 grantCode를 받아와, 우리의 BEServer에 전달하여 적절한 회원 정보를 받아오는 부분을 구현하고 있는데 이를 위와 같은 흐름으로 정리해보았다.

실 프로젝트 흐름을 MVVM 흐름에 맞춰 정리

먼저 1차적으로 ResourceServer에게 grantCode를 요청하는 흐름에서 논의점이 있었는데, 바로 Repository가 필요한가라는 점이었다.

이에 서로 여러 자료를 보며 Repository에 대한 학습을 더 진행하였고, 그 결과 로컬DB에 데이터가 있는지를 먼저 확인하고 없을 경우 네트워크 통신으로 데이터를 받아오는 작업까지 진행해주는 것이 Repository의 주역할이라는 걸 알게 되었다.
그러나 해당 흐름에서는 로컬DB에서 데이터를 찾는 작업이 필요없었고, 그저 네트워크에서 데이터를 받아오는 작업만 하면 되는 것이라 결국 Repository를 사용하지 않는 방향으로 결정되었다.

이런 1차 흐름이 잘 이루어져 GrantCodeUseCase가 적절한 DTO(Entity)를 VM에게 전달할 경우에는 didSet을 통해 2차 흐름으로 이어지는 메서드를 호출시키도록 하였다.
2차 흐름은 1차 흐름과 동일하게 전개되나, BEServer가 종착역인 점과 Repository가 필요하다는 점 등이 다소 달랐다.

Observable 객체

위의 1, 2차 흐름이 완료되면 VM에는 로그인한 유저에 대한 정보가 들어오는데, 프로퍼티에 해당 데이터가 할당되는 즉시 View(VC)가 이를 알아채고 적절하게 화면을 업데이트해줄 수 있도록 바인딩을 해야했다.

이에 Observable이라는 객체를 만들어 새로운 데이터가 들어오면 실행되어야 하는 메서드가 호출이될 수 있도록 구현하고자 했다.

final class Observable<T> {
    typealias Listener = ((T) -> Void)

    private var listener: Listener?
    private var value: T {
        didSet {
            listener?(value)
        }
    }

    init(_ value: T) {
        self.value = value
    }

    func bind(listener: @escaping Listener) {
        listener(value)
        self.listener = listener
    }

    func updateValue(value: T) {
        self.value = value
    }
}

해당 객체에는 두 개의 프로퍼티가 존재한다.
먼저 listener라는 프로퍼티는 제너럴 타입을 인자로 받아와 적절한 작업을 수행할 수 있는 클로저 타입이고 value 프로퍼티는 실제로 받아오거나 갱신될 Entity 타입을 할당받되, 할당되면 곧장 Listener 클로저에 해당 타입을 인자로 넘겨주고 작업을 실행시켜준다.

함수 또한 프로퍼티와 같이 두 개가 존재한다.
bind 메서드는 Listener 클로저를 인자로 받아와 기존 value를 넣어준 뒤, 자신의 listener 프로퍼티에 할당해주는 작업을 한다.
(ViewController에서 해당 메서드 호출.)

updateValue는 Entity가 update되었을 때, update된 Entity를 value 프로퍼티에 재할당시켜주는 작업을 수행한다.
(ViewModel에서 해당 메서드 호출.)

😚 Fourth Step.

마지막으로 우리는 이처럼 정리된 흐름을 클래스 다이어그램으로 정리를 하는 걸로 해당 흐름에 대한 설계를 마무리하고 실제 코드 리팩토링 작업에 들어갔다.

실 프로젝트 흐름을 클래스 다이어그램으로 정리

마치며..

MVVM 패턴과 프로토콜의 활용, 설계 후 작업 등 기존에 작업 방식과 전혀 다른 방식으로 이번 프로젝트를 진행하였는데 어려웠지만 매우 유익했고 즐거운 시간이었다.
그동안 기능이 많아질 수록 복잡성이 높아져 우왕좌왕하는 경우가 종종 있었는데 이런 식으로 먼저 설계를 하여 정리가 된다면 그럴 일도 많지는 않을 듯 하였고, 코드 하나하나가 훨씬 더 생동감 있게 느껴지기도 하였다.

아직도 익숙하지는 않지만 앞으로도 조금은 느릴 수도 있지만 이러한 방식으로 프로젝트를 진행하고자 한다.

profile
중2병도 iOS가 하고싶어

2개의 댓글

comment-user-thumbnail
2022년 12월 1일

굳굳

답글 달기
comment-user-thumbnail
2023년 6월 23일

너무 잘 읽었습니다 혹시 이미지들 출처를 알 수 있을까요? 혹시 직접 만드셨다면 저도 따로 사용해도 괜찮을까요??

답글 달기