iOS | (번역) Clean Architecture

iOSアプリ設計パターン入門 의 Clean Architecture의 관한 내용을 대부분 참고한 95%의 번역글입니다.
번역 글의 모든 권한은 저에게 없습니다 🙏


Clean Architecture?

Uncle Bob이 2012년 자신의 블로그에서 소개한 아키텍쳐 패턴입니다.

UI와 모델을 분리하는 것이 포인트인 MVC, MVP, MVVM 는 GUI Architecture이고,
반대인 System Architecture를 Clean Architecture 아키텍쳐는 전제로 합니다.

UI뿐만 아니라 Model의 내용 표현까지. 어플레케이션 전체에 관련된 아키텍쳐입니다.



Clean Architecure의 구성요소와 구조

어느 한 가지 기능이 구현된 어플리케이션을 구현해 낼 때,
Clean Architecture는 구현해 낼 기능(Domain)의 기술의 세부 내용에 포인트를 두고 4가지 구성요소로 나눕니다.

– Entity : 어플리케이션에 의존하지 않는 Domain(기능)에 관련된 데이터구조나 비지니스 로직
– Use Case : 어플리케이션 고유의 로직
– Interface Adaptor : [Use case], [Framework와 driver]에서 하용되는 데이터 구조를 서로 변환
– Framework와 Driver : DB, Web 등의 프레임워크나 툴의 세부 내용

이게 바로 동심원
🔗 출처


이 4가지 구성요소를 동심원 위에 배치합니다.
가장 순수하고 의존성이 없는 Entity를 동심원의 중심. 그 밖에 Use Case를 배치합니다.
반대로 외부요인(기술의 변화 등)으로 변화가 생기기 쉬운 DB/Web/Framework/OS와 같은 것(=Framework와 Driver)은 동심원의 가장 바깥쪽에 배치합니다.
Interface Adaptor는 변환층으로 Use case와 가장 바깥쪽 사이에 배치합니다.

그리고 의존의 방향은 바깥쪽에서 안쪽으로. 단방향으로만 합니다.



장점
이 구조 그대로 어플리케이션을 만들면 자주 수정(변경)되는 부분은 수정하기 쉽고,
유지하고 싶은 부분은 그대로 유지하기 쉽습니다.

Web API 서버나 Device drive에 의존하지 않는
동심원 가장 안쪽에 위치한 Entity나 Use Case는 당연히 Test 작성이 용이해집니다. 💪



iOS 어플리케이션에서 Clean Architecure를 사용한다면,

지금의 iOS 어플리케이션은 API 통신, DB 읽기/적기 등은 물론 구성자체가 복잡하고 거대해지고 있다.
안드로이드 어플리케이션과의 동시 개발이 보편화되었기 때문에 OS에 의존하지 않는 비지니스 로직을 같이 적는다면 보존성도 높아질 수 있다 !

논란의 여지는 존재하지만 Clean Architecture는
스템 전체에서 공통적인 부분(= OS에 의존하지 않는 비즈니스 로직)이 많이 존재하고
여러 플랫폼(= iOS와 안드로이드)의 개발이 필요할 때 사용된다면 효과가 발휘된다.




의존관계의 규칙

4가지 구성요소가 서로의 의존도가 어느정도인가를 기준으로 4개의 계층구조를 이루고있다.


1. Entity

Entity는 무언가를 처리할 때, Clean Architecture의 다른 구성요소에 의존하지 않는 비지니스 로직이고, 데이터 구조, 메소드들의 집합체이다.
동심원에서 외부의 구성요소에 의존하지 않기 때문에 UseCase나 다른 구성요소에 의해서 어떻게 사용되는지 전혀 신경쓰지 않는다.

Entity에서 무언가를 처리할 때 외부의 영향을 받지 않는 것에 대한 예
예를들어 해피포인트를 적립한다고 할 때,
파리바게트에서 빵을 사고 적립을 하던 베스킨라빈스에서 아이스크림을 사고 적립을해도 해피포인트 서버에 접근되어서 적립되는 코드는 변함이 없다.


2. Use Case
Use Case는 Entity를 사용해서 어플리케이션 고유의 비지니스 로직을 실현한다.
만들어지는 대상이 되는 어플리케이션에서만 필요한 무언가의 처리가 Use Case에 해당되는 영역이다.

🖍
Entity가 복수의 어플리케이션에 공유되고 !
Use Case는 만들어질 대상이 되는 어플리케이션에서만 사용된다 !


여러 개의 어플리케이션을 만들 때, Entity와 Use Case를 구분할 필요가 없다고 생각하는 사람도 있겠지만,
여기서 이야기하는 여러 개의 어플리케이션은 동시에 만들어지는 어플리케이션에 한정된 이야기가 아니라 하나의 어플리케이션이었다가 여러개가 될 수도 있는 가능성을 가지고 있다는 의미이기도 한다.

Use Case 에서는 UI에 관련되는 처리는 적지 않습니다 !!!!!!!!!!!!!!
메소드의 return 값, parameter와 같은 출입구(port)는 존재하더라도
port에 어떤 경로로 입력이 생겼고 출력이 어떻게 사용되는지는 몰라야한다.
(입출력이 어디서 생기고 없어지는지는 Interface Adaptor/Framework, Driver가 할일)


3. Interface Adaptor
Interface Adaptor는 본인의 계층의 안밖에 있는 Data나 event를 교환하기 위해 존재.

Use Case나 Entity에서 사용되는 Data를 SQL, UI용 Data로 변환하거나
반대로 DataBase나 Web으로부터 받은 Data를 Use Case나 Entity에서 사용될 수 있도록 변환하는
양쪽의 고리같은 역할을 한다.
Presenter, Controller가 바로 Interface Adaptor이다.

Interface Adaptor는 Use case와 동심원의 가장 바깥쪽을 연결해주는 역할을 담당한다.
(= User Case의 입출력 포트를 동심원 바깥쪽의 어느것과 연결시킬지도 결정하는 역할도 한다.)


4. Framework, Driver
동심원의 가장 바깥쪽에는 UI, Data Base, Device driver, Web API Client 등이 위치합니다.
(= 의존도가 가장 높음)

Flutter, React Native가 사용되는 프로젝트라면 각 플랫폼별 Navite code가 여기에 위치하게 된다.
UI나 실제로 작동되는 OS의 종류, Framework도 바로 여기 !
많이 사용되는 UIKit, Alamofire도 여기여기 ! 🙋🏻‍♀️

여기에 위치하게 되는 코드의 공통점은 비지니스 로직과는 전혀 관계가 없고 상황에 따라 만들어지는 수단이 변경할 가능성이 있다는 것이다.



계층구조간의 통신

🖍 동심원에서 바깥쪽에서 안쪽의 방향으로만 참조가 가능하다. 안쪽에서 바깥쪽으로 참조는 불가능.

안 쪽의 class가 바깥쪽의 class나 함수를 직접 참조하는 것은 불가능하다.



Dependency Inversion Priciple, DIP, 의존관계 역전의 법칙 (Interface Adaptor – Use Case)

동심원에서 Controller와 Use Case. 이 두 가지의 흐름도를 나타내면 위의 그림과 같다.

그림이 하는 일을 @iOS App 에서 한다면,
1. Web에서 새로운 데이터를 받은 Controller. 새 데이터를 Use Case로 넘긴다.
2. Use Case는 새로운 데이터를 가공한 결과를 Presenter에게 전달.
3. Presenter는 Use Case가 가공한 결과물을 화면에 표시.

이 때, 2번의 Use Case에서 Presenter로 데이터는 어떻게 전달해야 할까 🤔
– 의존관계를 유지한 채 역방향의 통신을 해야할 때에는 Dependency Inversion Priciple(DIP, 의존관계 역전의 법칙. 본문에서는 DIP로 표기)를 사용해야한다.

🧰 DIP 사용
1) 입력 / 출력에 필요한 protocol을 Use Case에서 정의하고 Controller, Presenter를 protocol에 따르도록 한다.
2) Controller가 Use Case에 Object의 참조를 return시킨다.
3) presenter가 Use Case로부터 Event를 간접적으로 받을 수 있게된다.



비동기가 전제로 설정된 메소드 (Framework – Interface Adaptor – Use Case)


Use Case에서 Web 서버에 Data를 요청은 위에서 이야기한 DIP를 사용해 한다고 할 때,
Framework, Driver에서는 Data 요청 결과를 Use Case에 어떻게 알려줘야할까.

이 경우 Use Case를 참조해서 메소드로 알려줘도 되는걸 어렵게 생각하냐는 사람도 있겠지만,
Use Case가 Data 요청 결과를 기다리는 비동기의 경우에는 꽤나 복잡해진다.

💡CompoletionHandler를 사용한다면 간단하게 해결할 수 있다 !

struct Tweet {}

// TwitterTimelineUseCase.swift
protocol TwitterTimelineUseCaseProtocol: AnyObject {
    func getTimeline(userID: String, completionHandler: @escaping (([Tweet]) -> Void))
}

final class TwitterTimelineUseCase {

    private weak var inputPort: TwitterTimelineUseCaseProtocol?
    private var currentUserTweets: [Tweet] = []

    func requestTimeline(userID: String) {
        inputPort?.getTimeline(userID: userID) { [weak self] newTweets in
            self?.currentUserTweets = newTweets
        }
    }
}

// TwitterTimelineUseCaseGateway.swift
final class TwitterTimelineUseCaseGateway {}

extension TwitterTimelineUseCaseGateway: TwitterTimelineUseCaseProtocol {

    func getTimeline(userID: String, completionHandler: @escaping (([Tweet]) -> Void)) {
        let requestResult: [Tweet] = ........

        completionHandler(requestResult)
    }

}

물론 completionHandler 대신 PromiseKit이나 Reactive 라는 선택지도 있지요.



Clean Architecture와 GUI Architecture
Clean Architecture는 System Architecture이기 때문에 대상 범위는 App 전체이다.

Clean Architecture는 UI를 컨트롤 가능한 계층이 있기는 하지만 비지니스 로직과는 완전히 분리되어 있다.
그 덕분에 Clean Architecture와 함께 GUI Architecture(MVC, MVP, MVVM)를 함께 사용하는 것이 가능하다.

“iOS | (번역) Clean Architecture”의 1개의 생각

iOS | VIPER? – unnnyong 에 답글 남기기 응답 취소

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중