Clean Architecture

개요

클린아키텍쳐는 모바일 클라이언트 앱이나 웹 어플리케이션에 주로 사용되는 소프트웨어 아키텍쳐이다. View와 다른 객체간의 관계와 역할에 초점을 맞춘 MVC나 MVVM 패턴과는 달리 애플리케이션의 동작에 필요한 요소들을 더 큰 계층으로 나누어 설명한다.

Clean Code의 저자로도 유명한, 클린아키텍쳐를 제창한 로버트 C 마틴은 2012년 처음 자신의 블로그에 이 아키텍쳐를 선보였다.(Clean Coder Blog)

서문에서 당대에 유명한 아키텍쳐들을 언급하며, 이 아키텍쳐들의 세부적인 요소들은 다를 수 있어도 주 목적은 관심사의 분리 라고 이야기 하고 있다. 아키텍쳐들은 이러한 분리를 소프트웨어를 계층으로 나눔으로써 달성하고, Business rules을 담당하는 계층과 인터페이스를 담당하는 계층은 최소한 하나는 가지고 있다고 이야기한다.

이렇게 구성된 아키텍쳐는 아래와 같은 특징을 갖는다.

1. 프레임워크의 독립성

아키텍쳐는 소프트웨어의 일부 라이브러리에 의존하지 않는다. 제한된 제약조건(의존하므로써 생기는)에 시스템을 밀어넣지 않고 프레임워크를 도구로써 사용할 수 있다.

2. Testable

비즈니스 규칙(비즈니스 로직)은 UI, DB, Web Server, 혹은 다른 외부적 요소와 관계없이 테스트 할 수 있다.

3. UI의 독립성

UI를 시스템의 다른 부분의 변경과 상관없이 쉽게 변경할 수 있다.이 일어날 수 있다. e.g) 비즈니스 로직을 그대로 유지하고, Web UI가 console UI로 변경

4. DB의 독립성

비즈니스 로직과 관계없이 DB를 교체할 수 있다. e.g) SQL로 구현된 DB를 MongoDB나 기타 다른 DB로 교체할 수 있다.

5. 외부 세계로부터의 독립성

비즈니스 로직은 소프트웨어 바깥에 존재하는 모든것에 대해 알지 못하고 독립적이다.

아래의 그림은 이러한 특성을 전부 반영하여 도식화 한 것이다.

의존성 규칙

위 그림에서 서로 다른 동심원들은 소프트웨어의 각기 다른 영역을 나타낸다. 일반적으로, 안으로 들어갈수록 고수준의 코드가 자리하게 된다. 가장 바깥의 원은 기계장치나 프레임워크와 가까운 매카닉한 코드, 가장 내부의 원은 소프트웨어의 핵심 정책이다. 간단한 예를 들면 "이미지를 즐겨찾기에 추가한다" 라는 행위는 고수준, "Favorite 코더데이터 모델에 해당 Image Entity를 create 한다"라고 생각 해 볼수 있다.

이 아키텍쳐에서 가장 우선시되는 규칙은 의존성 규칙이다. 이 규칙은 각 계층의 소스 코드는 자기보다 안쪽의 계층에만 의존할 수 있다는 것이다. 안쪽의 계층은 자신보다 바깥의 원에 대해 전혀 알지 못한다. 다시 말해, 바깥의 원에 정의되어있는 특정 클래스, 변수, 함수, 기타 요소의 이름이 안쪽 원의 코드에서 사용되어서는 안된다.

같은 이유로 바깥 영역에서 사용되는 데이터 유형은(특히, 그 유형이 바깥 원의 프레임워크에서 생성되었다면) 안쪽 원에서 사용되면 안된다.

클린아키텍쳐의 요소 with iOS

입력한 키워드와 연관된 이미지를 검색하고 편집할 수 있는 iOS 앱을 만든다고 가정하고 각 계층을 살펴보자. 이 앱은 웹 서버를 통해서 이미지를 검색할 수도 있고, 마음에 든 이미지를 로컬 DB에 저장할 수 있다.

Entities

엔티티는 메서드가 있는 객체일 수도 있고, 특정 문제 해결을 위한 데이터구조나 함수들의 집합일 수도 있다.

우리가 만드는 앱을 구현하기 위해서는 먼저 이미지를 담기 위한 Image 타입을 설계해야 할 것이고, 또한 이미지 편집을 위한 각종 그래픽 관련 객체들이 필요할 것이다.

한 마디로, 소프트웨어의 정수이기 때문에 가장 변할 요인이 적고 다른 외부 요인의 변경이 이 영역에 영향을 미칠 수 없다.

Use Cases

이 계층은 비즈니스 로직을 담당한다. 정의된 엔티티를 사용하여 비즈니스 로직을 캡슐화하고 구현한다. 쉽게 말하면, 소프트웨어의 사용 흐름을 정의하는 계층이다. 엔티티와 유즈케이스를 묶어 도메인 계층이라고도 일컫는다.

이 앱에서는 먼저 키워드를 통해 이미지를 검색하는 기능이 있으므로 키워드를 받아 특정 작업을 수행한 후 이미지들을 되돌려주는 형태의 유즈케이스를 구현해야 할 것이다. 또, 즐겨찾기한 이미지를 가져오기 위한 유즈케이스도, 이미지를 즐겨찾기하는 유즈케이스도 필요할 것이다.

따라서 보편적으로, 유즈케이스 계층에서는 DB나 Web 서버 애플리케이션같은 데이터의 원천(Data Source)에 접근하여 작업을 수행하는 일이 많다. 다만, DB나 Web 서버 애플리케이션은 위 도식상 외부에 위치하므로 유즈케이스는 해당 코드들을 직접 기술하지 못한다. 이를 해결하기 위해 보편적으로, Repository라는 계층을 설계하고 protocol을 사용하여 인터페이스화하여 의존성 역전을 달성하는 방식으로 해결한다.

추상화 된 Repository 프로토콜을 구체화 한 구체 Repository 객체들이 DB의 CRUD 규칙과 Web 애플리케이션의 엔드포인트를 알고있고, 이를 사용하여 유즈케이스에 필요한 결과를 돌려주는 형태로 구현한다.

Interface Adapters

이 계층의 역할은 유즈케이스와 엔티티에서 나가는 데이터를 외부 계층이 사용할 수 있는 유형으로 변환하고, 또 유즈케이스와 엔티티로 들어오는 데이터를 유즈케이스와 엔티티가 사용할 수 있는 유형으로 변환한다.

우리가 처음 설계한 Image라는 객체를 DB에 저장하거나 웹 서버를 통해 저장하려면 다른 형태의 데이터 유형으로 다뤄줘야 할 필요가 있을 수 있다. 반대 방향인 DB나 웹 서버를 통해 받아온 이미지를 도메인 계층에서 사용하는 Image객체로의 변환 또한 필요하다. 또는 해당 Image를 뷰로 표현하기 위해 별도의 작업이 필요할 수도 있다. 이때 이 계층이 그런 변환 역할을 담당한다. 각 계층은 바깥을 알아선 안된다는 규칙을 명심하자.

우리가 익히 알고있는 MVC 패턴, MVVM 패턴의 Controller와 ViewModel 그리고 위에서 설명한 Repository등이 그 역할을 담당한다고 볼 수 있을것이다.

Frameworks and Drivers

가장 바깥의 원에 해당하는 이 계층은 특정 프레임워크에 직접적으로 의존하는 부분이다. UIKit 혹은 SwiftUI에 의존하는 View, RealmSwift 혹은 CoreData에 의존하는 DB 등

경계를 넘나드는 규칙

위의 유즈케이스 문단에서 든 예시와 같이, 제어의 흐름과 의존의 방향이 일치하지 않는 경우가 있기 마련인데 이것을 인터페이스를 사용한 의존성 역전을 해서 극복하게 된다.

또한, 로버트 마틴은 경계를 넘기위해서는 단순한 데이터 구조여야 한다고 이야기하고 있다. 예를 들면, DB에서 사용하는 데이터 구조가 그대로 경계를 넘어 도메인 계층으로 들어와서 안된다는 이야기다.

주의사항 및 장점?

로버트 마틴은 위에서 도식화한 것은 말 그대로 도식일 뿐, 계층을 어떻게 분리하는지 몇개의 계층으로 세분화 할건지는 전적으로 설계하기 나름이라고 이야기 하고 있다. 중요한 것은 위에서 이야기한 의존성 규칙 을 지키는 것일뿐!

내가 클린아키텍쳐로 진행한 프로젝트는 아래의 구조를 띄고있다.

간략히 설명해보면, View에서 발생한 이벤트를 받아 ViewModel이 적절한 UseCase를 사용함으로써 데이터의 변경을 야기하게 된다. 이러한 데이터의 변경은 UseCase가 Repository에 적절한 CRUD나 메서드를 사용해서 요청하게 되며 해당 Repository는 적절한 DataSource에서 이 작업을 수행해서 결과를 반환하고 최종적으로 View가 이 결과에 따른 변경사항을 반영한다.

확장과 변경의 용이성

우리가 만약 DB 프레임워크로 CoreData를 선택하여 프로젝트를 진행했는데, 중간에 Realm을 사용하자는 의견이 나왔다고 생각해보자. 만약 위와 같이 계층분리가 이루어지지 않고 핵심 비즈니스로직과 해당 프레임워크의 코드가 같이 얽혀있는 상황이라면 관련된 코드 전체를 수정해야 하는 상황에 놓이게 된다. 계층 분리가 잘 되어있다면, DB를 담당하는 부분만 수정하면 되기에 확장과 변경에 대해 유리해지게 된다.
UI쪽도 마찬가지이다. UIKit 프레임워크를 사용한 UI부분도 완전히 분리되어 있기에, 맥 용 앱을 추가적으로 출시하고자 한다면 AppKit을 사용하여 View부분만 작성하면 맥 용 앱이 된다.

결론적으로 애플리케이션의 핵심이자 고수준, 추상적인 코드라고 할 수 있는 비즈니스로직이 변경되지 않으면서 세부사항들이자 저수준(e.g 뷰와 데이터소스) 코드를 변경, 확장할 수 있는게 큰 이점이라고 볼 수 있다. 계층의 세분화는 자유롭게 가능하므로 이를 어떻게 구성하여 좀 더 좋은 구조를 만들지는 전적으로 프로그래머의 몫이다.

Testable

계층의 의존성을 올바른 방향으로 설정했다면 우리는 테스터블한 코드를 갖게된다. iOS의 경우 가장 테스트를 힘들게 하는 요소인 UIKit에의 의존을 완전히 떼어버릴 수 있다는점(물론 이 점은 MVVM도 가능하다) 또 네트워크나 DB에 의존하지 않고 비즈니스 로직의 테스트가 가능하다는 점 등이 있을것이다.

결론

Clean Architecture는 디자인 패턴이라기보다는, 원론적인 의미에서의 프로그램 설계방법을 제시하는 아키텍쳐이다. 이에 근거하여 파생된 여러 아키텍쳐가 나오게 된다. (e.g. VIPER) 관심사의 분리올바른 의존방향이 무엇인지에 공부하기에 좋은 아키텍쳐라고 생각되어 꼭 공부해보시기를 권한다.

'Methodology > Architecture' 카테고리의 다른 글

[Architecture] MVVM 패턴  (0) 2022.10.30
[Architecture] MVC 패턴  (0) 2022.10.13