Clean Architecture가 무엇인가요?

민경준·2023년 3월 17일
1

Clean Architecture

목록 보기
1/2

🌟 Clean Architecture란?

클린 아키텍쳐는 Rober C. Martin(aka. Uncle Bob)이 엔터프라이즈 아키텍쳐에서 논의되던 내용을 집약시킨 개념을 블로그에 기재한 내용이다. 클린 아키텍쳐는 두 가지의 관점에서 볼 수 있다.

하나는 아키텍처 설계의 철학과 원칙이다.
Solid원칙 ㅡ단일 책임 원칙(Single Responsibility Principle)을 시작으로 한 다섯 가지 원칙ㅡ 등을 중심으로 이제까지 SW 설계에서 중요하게 거론되어온 다양한 원칙들을 일목요연하게 정리하고 있다.

또 하나는 이후 나올 과녁 다이어그램으로 유명한 아키텍처의 청사진이다. 이는 Hexagonal Architecture, Onion Architecture 등... 당시 널리 알려진 아키텍처들의 공통된 시스템을 그림을 통해 단일 개념으로 표현한 것이다. 위 아키텍처들은 공통된 목적을 갖고 있는데, 바로 관심사의 분리다. 소프트웨어의 계층을 나눔으로써 관심사를 분리했고 비즈니스 규칙을 위한 최소 하나 이상의 계층과 인터페이스를 위한 또 다른 계층을 가지고 있다.

위 아키텍처들의 공통된 시스템은 다음과 같다.

  1. 프레임워크 독립적: 이들 아키텍처는 소프트웨어 라이브러리 존재 여부에 의존하지 않는다.
    이는 시스템을 프레임워크의 한정된 제약에 억지로 집어넣는 대신 도구로써 사용하는 것을 가능하게 한다.

  2. 테스트 용이함: 비즈니스 규칙은 UI, 데이터베이스, 웹 서버, 기타 외부 요인없이 테스트 가능하다.

  3. UI 독립적: 시스템의 나머지 부분을 변경할 필요 없이 UI를 쉽게 변경할 수 있다. 예를들면 웹 UI는 비즈니스 규칙 변경 없이 콘솔 UI와 치환된다.

  4. 데이터베이스 독립적: 오라클 또는 SQL Server를 몽고, 빅테이블, 카우치 DB 등으로 바꿀 수 있다. 비즈니스 규칙은 데이터베이스에 얽매이지 않는다.

  5. 외부 기능 독립적: 실제로 비즈니스 규칙은 외부 세계에 대해 아무것도 모른다.

Uncle Bob은 이러한 특징들을 중심으로 각 계층을 어떻게 나누고 어떤 요소로 구성할 것인가에 대한 원칙들을 알려준다. 가운데로 갈 수록 높은 수준, 바깥으로 갈 수록 낮은 수준의 컴포넌트로, 이에 대한 효율적인 분리로 효과적인 설계가 가능하다는 점을 설명하고 있다.

이때 클린 아키텍처는 경계(Boundary)를 가장 중요하게 생각 하는데, 로버트 마틴은 경계에 대해 다음과 같이 설명한다.

“소프트웨어 아키텍처는 선을 긋는 기술이며, 나는 이러한 선을 경계(boundary)라고 부른다. 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.” - Robert C. Martin, Clean Architecture

다이어그램에서 화살표의 방향은 의존성을 뜻한다. 클린 아키텍처의 의존성은 밖에서 안으로 향하고, 바깥 원은 안쪽 원에 영향을 미치지 않는다. 경계의 바깥으로 갈수록 덜 중요하고 세부적인 영역으로 표현되며, 안으로 갈수록 고수준(좀더 추상화된 개념)으로 표현된다.

좀더 풀어 설명하면 고수준이 ‘운동을 한다’라면, 저수준은 ‘집에서 팔벌려뛰기 운동을 한다’로 표현할 수 있다.



🌟 Clean Architecture Rule

🔥 계층구조(Layered Architecture)


먼저 간단한 그림부터 살펴보자.

⚡️ 도메인(Domain)

도메인 레이어는 Business Rule이 존재하는 영역이다.
번역앱은 번역을 하고, 쇼핑몰 앱은 물건을 판다.
이렇듯 비지니스의 본질은 쉽게 바뀌지 않으므로 도메인은 Stable한 영역이다.

⚡️ 인프라(Infrastructure)

인프라 레이어는 UI, DataBase, API, Framework 등.. 이 존재하는 영역이다. 이는 도메인에 비하여 자주, 쉽게 바뀔 수 있다. 예를 들어 Button의 UI는 쉽게 바뀔 수 있지만 대출 계산 방식(Business Rule)은 쉽게 바꿀 수 없다.

UncleBob은 이렇게 경계를 두어 각 레이어를 분리하고, 관심사를 분리하는 규칙을 의존 규칙(Dependency Rule)으로 설명했다.

🔥 의존 규칙(Dependency Rule)

이들 동심원은 소프트웨어의 각기 다른 영역을 나타내고 있다. 대개, 바깥쪽으로 향할수록 고수준의 소프트웨어가 된다. 바깥쪽의 원은 메커니즘(Mechanism)이고 안쪽의 원은 정책(Policy)이다.

이 아키텍처를 기능하게 하는 중요한 규칙이 바로 의존 규칙이다. 이 규칙에 의해서 소스 코드는 안쪽을 향해서만 의존할 수 있다. 안쪽의 원은 바깥쪽의 원에 대해 전혀 알지 못한다. 특히, 바깥쪽의 원에서 선언된 어떠한 이름을 안쪽 원에서 참조해서는 안된다. 이는 함수, 클래스, 변수 등 이름이 붙은 소프트웨어의 엔티티 모든 것에 해당한다.

마찬가지로 바깥쪽의 원에서 사용하고 있는 데이터 포맷을 안쪽의 원에서 사용하지 않아야 한다. 특히 그러한 포맷이 바깥쪽 원에서 프레임워크에 의해 생성되고 있다면 바깥쪽 원의 어떠한 것도 안쪽의 원에 영향을 줘선 안된다.


그럼 이제 Clean Architecture의 다이어그램을 살펴보자.

계층구조 다이어그램에서 레이어가 더 세분화 되었다.
도메인은 Use Cases, Entities로 세분화 되었고 도메인과 인프라 사이에 인터페이스 어댑터(Interface Adapters)를 두어 둘 사이의 경계를 관리하도록 했다.

이렇게 다이어그램은 영역을 네 부분으로 시스템을 세분화 했다. 안쪽에서부터 하나씩 살펴보도록 하자.

⚡️ 엔티티(Entities)

  • 대규모 프로젝트 레벨의 비즈니스 규칙을 캡슐화한다.
  • 메서드를 갖는 객체 일 수도 있지만 데이터 구조와 함수의 집합일 수도 있다.
  • 바깥쪽에서 무엇이 변경되더라도 바뀌지 않는다. 즉, 외부로부터 영향을 받지 않는다.

⚡️ 유즈 케이스(Use Cases)

  • 시스템의 모든 유즈 케이스를 캡슐화하고 구현한다.
  • 엔티티로 부터의 혹은 엔티티에서의 데이터 흐름을 조합한다.
  • 엔티티에 영향을 주지 않을 것을 기대하며 데이터베이스, UI 또는 공통의 프레임워크의 변경으로부터 영향 받지 않을 것도 기대한다.
  • 프로젝트 레벨의 비즈니스 규칙을 사용해 유스케이스의 목적을 달성하도록 한다.

⚡️ 인터페이스 어댑터(Interface Adapter)

  • 외부 인터페이스(GUI, DB, WEB)에서 들어오는 데이터를 유즈 케이스와 엔티티에서 처리하기 편한 방식으로 변환한다.
  • 반대로 유즈 케이스와 엔티티에서 나가는 데이터를 외부 인터페이스에서 처리하기 편한 방식으로 변환하기도 한다.
  • GUI의 MVC 아키텍처를 완전히 내포한다.
  • 프리젠터, 뷰, 컨트롤러, 게이트웨이는 모두 여기에 속한다.

⚡️ 프레임워크와 드라이버(Framework & Drviers)

  • 데이터베이스나 웹 프레임워크 등 일반적으로 프레임워크나 도구로 구성된다.
  • 안쪽의 원과 통신할 연결 코드 이외에는 별다른 코드를 작성하지 않는다.
  • 시스템의 핵심 업무와는 관련 없는 상세한 정보가 무엇이든 여기에 둔다.

🔥 4개의 원이 아니면 안되는가?

아니다. 이 원은 컨셉을 전하기 위한 수단이다. 이 4가지 이외에도 무엇인가 필요할 가능성이 있다. 정확히 4가지가 아니면 안된다는 규칙은 없다. 하지만 의존 규칙은 항상 적용된다. 소스 코드의 의존성은 항상 안쪽으로 향해야 한다. 안쪽으로 향해감에 따라 추상화 수준은 올라간다. 가장 바깥쪽의 원은 저수준의 구체적인 상세 정보를 담는다. 안쪽으로 이동해가면서 소프트웨어는 추상화 되고 고수준의 정책을 캡슐화한다. 가장 안쪽에 있는 원은 무엇보다 일반성이 있다.

🔥 교차경계(Cross Boundaries)

위 그림은 어떤식으로 원의 경계가 교차하는지 보여주는 예다. 이것은 컨트롤러와 프레젠터가 다음 계층인 유스케이스와 어떻게 대화하는지 보여준다.
제어의 흐름에 주의하길 바란다. 컨트롤러에서 시작해 유스케이스를 거쳐 프레젠터에서 실행됨을 알 수 있다. 하지만 소스코드의 의존성에도 주의했다. 모두 안쪽의 유스케이스를 향하고 있다. 어떻게 가능한것일까?

우리는 이 분명한 모순을 의존 관계 역전의 원칙(Dependency Inversion Principle)으로 해결하는 경우가 많다. 예를들어 Swift와 같은 언어는 프로토콜과 상속 관계를 조합해 소스 코드의 의존성이 경계를 걸치고 있는 오른쪽 지점에서 제어 흐름이 반대하도록 한다.

다시 예를들어 유스케이스가 프리젠터를 호출할 필요가 있는 경우를 생각해보자. 하지만 이때의 호출은 직접 이뤄질 수 없다. 왜냐하면 ”의존성 규칙 : 바깥쪽의 이름을 안쪽에서 언급할 수 없다” 를 위반하기 때문이다. 때문에 유스케이스는 안쪽 원에 있는 프로토콜(유스케이스 출력 포트, Use Case Output Port)을 호출한다. 그리고 원 바깥쪽의 프레젠터는 이 프로토콜을 구현한다.

이와 똑같은 테크닉이 아키텍처의 경계가 교차되는 곳에서 사용된다. 동적인 다형성의 이점을 이용해 소스 코드의 의존성이 제어 흐름의 반대가 되도록 한다. 이렇게 하면 제어의 흐름이 어떤 방향으로 진행되든지 상관없이 의존성 규칙을 지킬 수 있다.

🔥 어떤 데이터가 경계를 교차하는가(What Data Crosses The Boundaries)

경계 간의 데이터를 전달할 때 무엇을 전달해야 하는가에 대한 이야기이다. 의존성 규칙을 지키기 위해서는 우리가 사용하는 단순하고, 고립된 형태의 데이터 구조를 사용하는 것을 추천한다. 만약 DB의 형식의 데이터 구조 또는 Framework에 종속적인 데이터 구조가 사용되게 된다면, 이러한 저수준의 데이터 형식을 고수준에서 알아야 하기 때문에 의존성 규칙을 위반하게 된다.

예를 들어 여러 데이터베이스 프레임워크는 쿼리에 응답하여 편리한 데이터 포맷을 반환한다. 이것을 행 구조(RowStructure)라고 부르자. 이 행 구조를, 경계를 넘어 안쪽으로 전달하지 않기를 원한다. 이는 의존성 규칙을 위반한다. 왜냐하면 바깥쪽 원에 관한 무언가를 안쪽의 원이 알도록 강제하기 때문이다.

때문에 경계를 넘어 데이터를 전달할 때엔 항상 안쪽의 원이 다루기 쉬운 데이터 형식이어야 한다.

🌟 글을 마치며

이런 간단한 규칙을 따르는 것은 어렵지 않다. 그리고 머리가 아프지 않도록 도와 줄 것이다. 소프트웨어를 계층으로 나누고 의존성 규칙을 따름으로써 본질적으로 테스트하기 쉬운 시스템을 만들 수 있고 의존성 규칙이 가져오는 이점도 얻을 수 있다. 시스템의 외부 부품(데이터베이스나 웹 프레임워크 등)이 낡았다면 그러한 부분도 최소한의 노력으로 바꿀 수 있다.

사실 처음엔 Clean Architecture라는게 마냥 추상적으로만 다가왔지만 여러 글을 읽으며 어떤 개념인지, 어떻게 소스코드에 적용해야 할지 감이 잡히기 시작했다. 이번엔 Clean Architecture의 기본적인 구성에 대해서 공부를 했지만 실질적으로 해당 다이어그램에 딱 맞춰서 설계하기란(꼭 4개의 원이 아니여도 된다고 했으니...) 어려운 법이다. Clean Architecture의 철학과 원칙, DIP와 같은 SOLID 규칙들을 지키는것이 더 중요하다고 생각하기 때문에 다음엔 이런것들이 Swift 혹은 모바일 앱에 어떻게 적용되는지에 대해 알아보도록 하자.





Reference

profile
iOS Developer 💻

0개의 댓글