Clean Architecture란, Uncle Bob(Robert Cecil Martin)1이 정립한 시스템 아키텍쳐2에 대한 아이디어를 의미한다.
기존에 존재하던 시스템 아키텍쳐들의 특징을 파악하고, 이를 하나의 아이디어로 통합하고자 하는 것에 의미가 있다.
일단은 전체 구조를 2가지로 나누어 보자.
Inner Circle은 Outer Circle에 대해 아무것도 몰라야(=종속성이 없어야), 안정적이고 가변적인 각 계층의 특성이 지켜지게 된다.
이런 식으로 경계를 두어, 관심사를 토대로 각 계층을 분리하여 아키텍쳐를 동작시키게 되는데, 여기서 가장 기본이 되는 규칙이 의존성 규칙(Depedency Rule)이다.
소스코드의 종속성(=의존성)은 저수준에서 고수준으로, 즉 동심원들의 안쪽 방향으로만 향해야 한다는 개념이다.
예를 들어, 서점이라는 개념을 서비스로 구현한다고 해보자.
이를 위해서는 비즈니스 로직이 세부 구현에 영향을 받지 않아야 된다.
즉, 고수준(비즈니스 로직)이 저수준(세부구현)에 의존성이 있어서는 안된다는 결론이 도출된다.
위에 나왔던 2개의 동심원을 특성에 따라 더 쪼개보았다.
전사적 비즈니스 규칙(Enterprise wide business rules)을 담고 있다.
외부 변화가 있어도 가장 변경될 가능성이 적고, 가장 고수준(=가장 종속성이 적은) 규칙들을 서술한다.
현실 세계를 직접적으로 표현한 내용들이 담겨 있으며, 그에 따라 특정 어플리케이션에도 종속되지 않아야 한다.
코드로 표현할 때는, 정보(=Attribute)와 행위(=Method)를 모두 담아낼 수 있다.
Kotlin 코드로 예를 들자면, 아래와 같이 책 자체에 대한 정보를 모아둘 수 있을 것.
data class Book(
val name: String,
val author: String,
val ISBN: String,
val revision: String?,
) {
fun getRevisionName(): String {
return revision ?: name
}
}
어플리케이션 비즈니스 규칙(Application specific business rules)을 담고 있다.
시스템의 동작을 사용자의 관점에서 시나리오화 해서, 어플리케이션의 동작을 서술한다.
엔티티를 참조하고 그와 상호 작용하여 어플리케이션이 이루고자 하는 목적을 달성케 하되, 엔티티 그 자체에 영향을 주어서는 안된다.
당연하겠지만, 사용하는 데이터베이스나 프레임워크가 바뀌더라도 이 단계까지는 변할 필요가 없다.
물론, 어플리케이션 자체의 목적이 바뀐다면 이 계층의 구현들도 바뀔 수 있겠지.
비즈니스 로직에서 사용되는 데이터 형식과, 데이터베이스 등의 외부 세계에서 사용되는 데이터 형식 사이의 변환 역할을 담당한다.
예를 들자면, Kotlin 데이터 클래스로 관리되던 비즈니스 로직 데이터를 JSON Content-Type을 가지는 HTTP Response로 변환한다던가.
어댑터와 연결된 외부 세계가 바뀌더라도, 해당 외부 세계와 연결된 어댑터만 적절하게 바꿔준다면 전체 어플리케이션은 정상 동작 되어야 함.
UI / 데이터베이스 / 웹 프레임워크 / 디바이스 / 네트워크 등 모든 외부 세계를 포함한다.
일반적으로, 이 계층에서는 어댑터 등의 내부 계층과 상호 작용하기 위한 결합용 코드 정도만 작성하게 된다.
인프라스트럭쳐 자체는 쉽게 변경할 수 있어야 하지만, 그 변경이 어플리케이션의 동작에는 절대 영향을 주어서는 안된다.
무조건 상술한 4개 계층으로 나누어야 하는 것은 아니다!
그러나, 각 계층 간의 참조는 한 방향으로만 되어야 한다!
A -> B
와 B -> C
혹은 A -> C
도 가능하지만 C -> B
나 C -> A
는 절대 불가함을 명심하자.각 계층 간에는 역할과 책임이 명확한 인터페이스를 마련해보자!
의존성은 동심원의 밖에서 안으로의 한 방향으로만 진행될 수 있지만, 어플리케이션의 특성에 따라서 제어의 흐름은 반대가 될 수 있다.
MVC 구조를 가지는 어떤 웹 서비스 시스템이 있다고 예를 들어보자.
Model은 실제 비즈니스 로직이 들어가는 부분으로, Domain 계층에 해당한다.
Controller는 HTTP Request를 처리하고, View는 UI를 변경하기에 Infrastructure 계층에 해당한다.
이때, 프로그램의 동작 순서는 Controller에서 Request 처리
, Model에서 비즈니스 로직 수행
, View에서 UI에 데이터 반영
이 되겠지.
Controller에서 Model로 가는 흐름은 참조가 저수준에서 고수준으로 가기에 Clean Architecture를 위배하지 않지만...
Model에서 View에 데이터를 전달하는 흐름은...?
Model에서 View로 데이터를 전달하기 위해서는, 위 그림처럼 Model이 View를 참조해야 한다고 생각할 수 있다.
그렇게 되면 참조가 고수준에서 저수준으로 가기 때문에 Clean Architecture를 위배하게 된다.
이 문제를 어떻게 해결할 수 있을까?
고수준의 계층에서 필요한 요구 사항만을 명세해놓은 중간 인터페이스를 두는 방식으로 해결할 수 있다.
전체 구조 관점에서도, 고수준의 요구사항 명세와 저수준의 세부 사항이 분리 됨으로서 구현의 변경이 있어도 동작이 바뀌지 않게 된다.
[1]: 객체 지향 설계에서 지켜야 할 5개의 원칙인, SOLID 원칙을 구상해낸 사람이기도 함.
[1]: 아키텍쳐라는 개념에 대해 쉽게 말하자면, 시스템의 각 구성 요소가 어떻게 나뉘고 어떻게 배치되고 어떻게 상호 작용하는지에 대한 방법론이라고 표현할 수 있음.