728x90

만들면서 배우는 클린 아키텍처를 읽고 공부한 내용을 정리해 보자.

단일 책임 원칙


하나의 컴포넌트는 오로지 한 가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.

보통 단일 책임 원칙은 위의 내용처럼 해석하기 쉽지만 실제 의도는 아래에 가깝다

컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.

 

책임은 사실 한 가지 일만 하는 것보다는 변경할 이유로 해석해야 한다. 컴포넌트가 변경할 이유가 오로지 한 가지라면 컴포넌트는 자연스럽게 한 가지 일만 하게 된다. 변경할 이유가 오직 한 가지라는 것은 아키텍처에서 어떤 의미일까?  컴포넌트를 변경할 이유가 한 가지라면 어떤 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 전혀 신경 쓸 필요가 없다. 소프트웨어가 변경되더라도 기대한 대로 동작할 것이기 때문이다.

 

하지만 변경할 이유라는 것은 컴포넌트 간의 의존성을 통해 쉽게 전파된다. 컴포넌트의 의존성 각각은 해당 컴포넌트를 변경하는 이유 하나씩에 해당한다. 컴포넌트 E를 변경할 유일한 이유는 E의 기능을 바꿔야 할 때뿐이다. 반면 컴포넌트 A는 모든 컴포넌트에 의존하고 있기 때문에 다른 어떤 컴포넌트가 바뀌든지 같이 바뀌어야 한다.

 

 

의존성 역전 원칙


 

계층형 아키텍처에서 계층 간 의존성은 다음 계층인 아래 방향을 가리킨다. 단일 책임 원칙을 고수준에서 적용할 때 상위 계층들이 하위 계층들에 비해 변경할 이유가 더 많다는 것을 알 수 있다. 그러므로 영속성 계층에 대한 도메인 계층의 의존성 때문에 영속성 계층을 변경할 때마다 잠재적으로 도메인 계층도 변경해야 한다.

 

 

그러나 도메인 코드는 애플리케이션에서 가장 중요한 코드다. 어떻게 이 의존성을 제거할 수 있을까? 바로 의존성 역전 원칙이다.

코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.

 

사실 의존성의 양쪽 코드를 모두 제어할 수 있을 때만 역전시킬 수 있다. 만약 서드파티 라이브러리에 의존성이 있다면 제어할 수 없기 때문에 역전시킬 수 없다. 도메인 코드와 영속성 코드 간의 의존성을 역전시켜서 영속성 코드가 도메인 코드에 의존하고, 도메인 코드를 “변경할 이유”의 개수를 줄여보자.

 

 

엔티티는 도메인 객체를 표현하고 도메인 코드는 이 엔티티들의 상태를 변경하는 일을 중심으로 하기 때문에 먼저 엔티티를 도메인 계층으로 올린다. 그러나 이제는 영속성 계층의 리포지토리가 도메인 계층에 있는 엔티티에 의존하기 때문에 두 계층 사이에 순한 의존성이 생긴다. 따라서 DIP를 적용하여 도메인 계층에 리포지토리 대한 인터페이스를 만들고, 실제 리포지토리는 영속성 계층에서 구현하게 하는 것이다.

 

클린 아키텍처


로버트 마틴은 클린 아키텍처라는 용어를 정립했다. 클린 아키텍처에서는 설계나 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, ui 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다고 이야기했다. 이 말은 도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함을 의미한다.

 

 

대신 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있다. 클린 아키텍처에서 중요한 규칙은 의존성 규칙으로, 계층 간의 모든 의존성이 안쪽으로 향해야 한다는 것이다.  아키텍처의 코어에는 주변 유스케이스에서 접근하는 도메인 엔티티들이 있다. 유스케이스는 앞에서 서비스라고 불렀던 것들인데, 단일 책임을 갖기 위해 조금 더 세분화돼 있다. 이를 통해 넓은 서비스 문제를 피할 수 있다.

 

도메인 코드에서는 어떤 영속성 프레임워크나 UI 프레임워크가 사용되는지 알 수 없기 때문에 특정 프레임워크에 특화된 코드를 가질 수 없고 비즈니스 규칙에 집중할 수 있다. 그래서 도메인 코드를 자유롭게 모델링할 수 있다. 예를 들어, DDD를 순수한 형태로 적용해 볼 수도 있다.

 

하지만 클린 아키텍처에는 외부 계층과 철저하게 분리돼야 하므로 애플리케이션 엔티티에 대한 모델을 각 계층에서 유지보수해야 한다. 가령 영속성 계층에서 ORM 프레임워크를 사용한다고 해보자. 도메인 계층은 영속성 계층을 모르기 때문에 도메인 계층에서 사용한 엔티티 클래스를 영속성 계층에서 함께 사용할 수 없고 두 계층에서 각각 엔티티를 만들어야 한다. 즉, 도메인 계층과 영속성 계층이 데이터를 주고받을 때, 두 엔티티를 서로 변환해야 한다는 뜻이다. 이는 도메인 계층과 다른 계층들 사이에서도 마찬가지다. 하지만 이것은 바람직한 일이다. 이것이 바로 도메인 코드를 프레임워크에 강결합이 제거된 상태이다.

예를 들어 JPA에서 인자가 없는 기본 생성자를 강제하는 것

 

헥사고날 아키텍처


 

헥사고날 아키텍처는 애플리케이션 코어가 각 어댑터와 상호작용하기 위해 특정 포트를 제공하기 때문에 포트 어댑터 아키텍처라고도 불린다. 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다. 육각형에서 외부로 향하는 의존성이 없기 때문에 클린 아키텍처에서 제시한 의존성 규칙이 그대로 적용된다는 점을 주목하자. 대신 모든 의존성은 코어를 향한다.

 

육각형 바깥에는 애플리케이션과 상호작용하는 다양한 어댑터들이 있다. 애플리케이션 코어와 어댑터들 간의 통신이 가능해지려면 코어가 각각의 포트를 제공해야 한다.

  • 코어를 주도하는 어댑터(driving adapter)에게는 포트가 코어에 있는 유스케이스 클래스 중 하나에 의해 구현되고 어댑터에 의해 호출되는 인터페이스가 될 것이다.
  • 코어에 의해 주도되는 어댑터(driven adapter)에게는 포트가 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스가 될 것이다.

 

헥사고날 아키텍처도 클린 아키텍처처럼 계층으로 구성할 수 있다. 가장 바깥쪽에 있는 계층을 애플리케이션과 다른 시스템 간의 번역을 담당하는 어댑터로 구성되어 있다. 다음으로 포트와 유스케이스 구현체를 결합해서 애플리케이션 계층을 구성할 수 있다. 마지막 계층에는 도메인 엔티티가 위치한다.

 

결국 핵심은? 의존성


결국 어떤 아키텍처라고 불리든 의존성을 역전시켜 도메인 코드가 다른 바깥쪽 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 모든 문제로부터 도메인 로직의 결합을 제거하고 코드를 변경할 이유의 수를 줄일 수 있다. 도메인 코드는 비즈니스 문제에 딱 맞도록 자유롭게 모델링 될 수 있고, 영속성 코드와 UI 코드도 영속성 문제와 UI 문제에 맞게 자유롭게 모델링 될 수 있다.

 

728x90
728x90

만들면서 배는 클린 아키텍처를 읽고 공부한 내용을 정리해 보자.

 

계층형 아키텍처


계층으로 구성된 웹 애플리케이션은 누구나 개발해 본 적 있을 것이다.

계층일 이용하는 사고방식은 컴퓨터 과학 수업이나 튜토리얼, 모범사례를 통해 주입되어 왔다.

 

전통적인 웹 애플리케이션 구조

 

사실 계층형 아키텍처는 견고한 아키텍처 패턴이다. 계층을 잘 이해하고 구성한다면 웹 계층이나 영속성 계층에 독립적으로 도메인 로직을 작성할 수 있다. 기존 기능에 영향을 주지 않고 새로운 기능을 추가할 수도 있다. 잘 만들어진 계층형 아키텍처는 선택의 폭을 넓히고 , 변화하는 요구사항과 외부 요인에 빠르게 적용할 수 있게 해 준다. 로버튼 마틴에 의하면 이것이 바로 아키텍처의 전부다(클린 아키텍처)

 

그렇다면 계층형의 문제점은 무엇일까? 계층형 아키텍처는 코드에 나쁜 습관들이 스며들기 쉽게 만들고 시간이 지날수록 소프트웨어를 점점 더 변경하기 어렵게 만드는 허점들을 노출한다.

 

데이터베이스 주도 설계를 유도


정의에 따르면 계층형 아키텍처의 토대는 데이터베이스다. 웹 계층은 도메인 계층에 의존하고, 도메인 계층은 영속성 계층에 의존하기 때문에 자연스레 데이터베이스에 의존하게 된다. 모든 것이 영속성 계층을 토대로 만들어진다. 이런 방식은 다양한 이유로 문제를 초래한다.

 

우리가 만드는 애플리케이션의 대부분의 목적은 무엇인가, 바로 비즈니스를 관장하는 규칙이나 정책을 반영한 모델을 만들어서 사용자가 이러한 규칙과 정책을 더욱 편리하게 활용할 수 있게 한다. 이때 우리는 상태가 아니라 행동을 중심으로 모델링한다. 상태가 중요한 요소이긴 하지만 행동이 상태를 바꾸는 주체이기 때문에 행동이 비즈니스를 이끌어간다.

 

그렇다면 왜 도메인 로직이 아닌 데이터베이스를 토대로 아키텍처를 만드는 걸까? 그동안 만들어 본 애플리케이션의 유스케이스를 도메인 로직이 아니라 영속성 계층을 먼저 구현했을 것이다. 데이터베이스의 구조를 먼저 생각하고 이를 토대로 도메인 로직을 구현했을 것이다.

계층형 아키텍처에서는 합리적인 방법이다. 의존성에 방향에 따라 자연스럽게 구현한 것이기 때문이다. 하지만 비즈니스 관점에서는 전혀 맞지 않는 방법이다. 먼저 도메인 로직을 만들어야 한다. 그래야 우리가 로직을 제대로 이해했는지 확인할 수 있으며, 도메인 로직이 맞다는 것을 확인한 후에 이를 기반으로 영속성 계층과 웹 계층을 만들어야 한다.

 

데이터베이스 중심 아키텍처가 만들어지는 가장 큰 원인은 ORM 프레임워크를 사용하기 때문이다. ORM 프레이워크가 나쁘다는 것이 아니라 ORM 프레임워크를 계층형 아키텍처와 결합하면 비즈니스 규칙을 영속성 관점과 섞고 싶은 유혹을 쉽게 받는다.

 

 

도메인 계층에서 이러한 영속성 계층 속 엔티티에 접근할 수 있으며 사용되기 마련이다. 이렇게 되면 영속성 계층과 도메인 계층 사이에 강한 결합이 생긴다. 서비스는 영속성 모델을 비즈니스 모델처럼 사용하게 되고 이로 인해 도메인 로직뿐만 아니라 즉시로딩/지연로딩, 데이터베이스 트랜잭션, 캐시 플러시 등등 영속성 계층과 관련된 작업들을 해야만 한다. 영속성 코드가 사실상 도메인 코드에 녹아들어 가서 둘 중 하나만 바꾸는 것이 어려워진다.

 

지름길을 택하기 쉬워진다.


계층형 아키텍처에서 전체적으로 적용되는 유일한 규칙은, 특정한 계층에서는 같은 계층에 있는 컴포넌트나 아래에 있는 계층에만 접근 가능하다는 것이다. 만약 상위 계층에 컴포넌트에 접근해야 한다면 간단하게 해당 컴포넌트를 계층아래로 내려버리면 된다. 딱 한번 이렇게 하는 것은 괜찮을 수 있지만 보통 영속성 계층은 수년에 걸친 개발과 유지보수로 아래 그림처럼 될 가능성이 높다.

 

 

영속성 계층에서는 모든 것에 접근 가능하기 때문에 시간이 지나면 점점 비대해진다. 영속성 계층은 컴포넌트를 아래 계층으로 내릴수록 비대해진다. 어떤 계층에도 속하지 않는 것처럼 보이는 헬퍼 컴포넌트나 유틸리티 컴포넌트들이 이처럼 아래 계층을 내릴 가능성이 큰 후보다.

 

테스트하기 어려워진다.


계층형 아키텍처를 사용할 때 일반적으로 나타나는 변화의 형태는 계층을 건너뛰는 것이다. 엔티티의 필드를 단 하나만 조작하면 되는 경우에 웹 계층에서 바로 영속성 계층에 접근하면 도메인 계층을 건드릴 필요가 없지 않을까?

 

 

도메인 계층을 건너뛰는 것은 도메인 로직을 코드 여기저기에 흩어지게 만든다. 웹 계층 쪽 유스케이스가 확장되는 경우 아무리 간단한 것에 불과하더라도 도메인 로직을 웹 계층에 구현하게 된다. 따라서 애플리케이션 전반에 걸쳐 책임이 섞이고 핵심 도메인 로직들이 퍼져나갈 확률이 높다.

 

유스케이스를 숨긴다.


 

기능을 추가하거나 변경할 적절한 위치를 찾는 일이 빈번하기 때문에 아키텍처는 코드를 빠르게 탐색하는 데 도움이 돼야 한다.

하지만 계층형 아키텍처는 말했듯이 도메인 로직이 여러 계층에 걸쳐 흩어지기 쉽다. 이럴 경우 새로운 기능을 추가할 적당한 위치를 찾는 일이 어려워진 상태이다. 계층형 아키텍처는 도메인 서비스의 ‘너비’에 관한 규칙을 강제하지 않기 때문에 시간이 지나면 여러 개의 유스케이스를 담당하는 아주 넓은 서비스가 만들어지기도 한다.

 

 

넓은 서비스는 영속성 계층에 많은 의존성을 갖게 되고, 다시 웹 레이어의 많은 컴포넌트가 이 서비스에 의존하게 된다. 그럼 서비스를 테스트하기도 어려워지고 작업해야 할 유스케이스를 책임지는 서비스를 찾기도 어려워진다. 고도로 특화된 좁은 도메인 서비스가 유스케이스 하나씩만 담당하게 한다면 이런 작업들이 얼마나 수월해질까? UserService에서 사용자 등록 유스케이스를 찾는 대신 RegisterUserService를 바로 열어서 작업을 시작하는 것처럼 말이다.

 

동시 작업이 어려워진다.


계층형 아키텍처는 동시 작업 측면에서는 그다지 도움이 되지 않는다. 계층형 아키텍처에서는 모든 것이 영속성 계층 위에 만들어지기 때문에 특정 기능은 동시에 한 명의 개발자만 작업할 수 있다. 인터페이스를 먼저 같이 정의 후 작업을 할 순 있지만 영속성 로직과 도메인 로직이 뒤섞여서 각 측면을 개별적으로 작업하기 힘들고, 또 코드에 넓은 서비스가 있다면 서로 다른 기능을 동시에 작업하기는 더욱 어렵다.

 

유지보수 가능한 소프트웨어를 만드는데 어떻게?


물론 계층형 아키텍처도 올바르게 구축하고 몇 가지 추가적인 규칙들을 적용하면 유지보수가 쉬워지며 코드를 쉽게 변경하거나 추가할 수 있다. 하지만 앞에서 살펴봤듯이 잘못된 방향으로 흘러가도록 쉽고 용인하는 구조이다.

 

따라서 계층형 아키텍처로 만들든 다른 아키텍처 스타일로 만들든, 계층형 아키텍처의 문제점들을 염두에 두면 지름길을 택하지 않고 유지보수하기에 더 쉬운 솔루션을 만드는 데 도움이 될 것이다.

 

 

728x90
728x90

서론


전통적인 레이어드 아키텍처로 프로젝트를 개발하며 점점 문제점들이 보이기 시작했다. 서비스가 조금씩 커지거나 요구사항의 확장이 진행되며 점점 더 복잡해지면서 유지보수에 점점 더 많은 시간이 쓰이고 있었다. 많은 서비스를 담당하는 엄청난 서비스, 영속성 계층인 데이터베이스에 의존성이 점점 커지는 것 등 이러한 문제점을 해결하기 위한 고민이 있던 와중 평소에 소프트웨어 개발에서 도메인 주도 설계, 클린 아키텍처, 헥사고날 아키텍처라는 용어를 많이 들어왔으나 "클린 아키텍처는 뭐고, 헥사고날 아키텍처는 뭐지?, 둘의 차이는 무엇이고 그래서 어떤 아키텍처를 사용하란 걸까? " 하며 항상 헷갈려했다.

이러한 아키텍처들이 왜 등장하게 되었으며, 전통적인 계층형 아키텍처의 문제점을 해결할 수 있을까 싶어 찾아보게 되었다.

조금 조사하던 와중 DDD는 조금 더 큰 영역의 내용인 것 같아 추후에 다시 작성하도록 하고 먼저 클린아키텍처에 대해서 가볍게 훑은 내용들을 정리하고자 한다.

 

 

왜 소프트웨어 아키텍처가 중요할까?


정말 소프트웨어 아키텍처가 중요한 걸까? 소프트웨어가 제공하는 가치는 두 가지가 있다고 한다. 바로 기능과 구조이다. 기능과 구조에서 조금 더 중요한 것은 무엇일까?? 평소에는 기능이라고 생각해 왔다. 우리가 만드는 애플리케이션의 대부분의 목적은 바로 비즈니스를 관장하는 규칙이나 정책을 반영한 모델을 만들어서 사용자가 기능들을 편리하게 사용할 수 있게 만드는 것이라고 생각했기 때문이다.

 

하지만 로버틴 C. 마틴은 구조의 중요성은 언급한다. 왜일까? 바로 우리가 원하는 것은 더 정확하게, 더 빠르게, 더 많이 기능을 추가하기 위해 코드를 읽고, 이해하고, 수정해야 하기 때문이다. 즉, 시스템을 만들고 유지보수하는데 투입되는 인력을 최소화하는 것이다. 잘 생각해 보면 레거시 프로젝트이든 최근에 만든 프로젝트이든 새로운 코드를 짜는 것보다 기존 코드를 바꾸는데 훨씬 더 많은 시간을 쓰는 것이 생각났다.

즉 구조가 좋다는 것은 수정의 비용이 적다는 것이다.

 

좋은 아키텍처 - 1. 계층형 아키텍처


그렇다면 좋은 아키텍처에는 무엇이 있을까?  첫 번째로 계층형 아키텍처다. 사실 계층형 아키텍처는 계층을 잘 이해하고 구성한다면 웹 계층이나 영속성 계층에 독립적으로 도메인 로직을 작성할 수 있다고 한다.  따라서 기존 기능에 영향을 주지 않고 새로운 기능을 추가할 수도 있다.

 

하지만 계층형 아키텍처의 가장 큰 단점은 도메인 계층이 영속성 계층을 의존하는 데이터베이스 주도 설계를 유도한다는 것이다. 도메인 로직을 여러 계층에 흩어지게 만들기 쉬운 아키텍처이다. 기능 기반으로 패키지를 구성하여도 계층적 구조는 동일하다.

 

https://www.youtube.com/watch?v=g6Tg6_qpIVc

 

 

좋은 아키텍처 - 2. 클린 아키텍처


로버트 마틴은 클린 아키텍처라는 용어를 정립했다. 클린 아키텍처에서는 설계나 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, ui 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다고 이야기했다.

 

이 말은 도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함을 의미한다. 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있는 아키텍처이다.

https://www.youtube.com/watch?v=g6Tg6_qpIVc

 

헥사고날 아키텍처?


헥사고날 아키텍처는 애플리케이션 코어가 각 어댑터와 상호작용하기 위해 특정 포트를 제공하기 때문에 포트 어댑터 아키텍처라고도 불린다. 육각형이란 단어에는 아무런 뜻도 없으며 중요한 것은 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다. 육각형에서 외부로 향하는 의존성이 없기 때문에 클린 아키텍처에서 제시한 의존성 규칙이 그대로 적용된다는 점을 주목하자. 대신 모든 의존성은 코어를 향한다.

 

https://www.youtube.com/watch?v=g6Tg6_qpIVc

 

클린아키텍처의 애매함

클린 아키텍처는 핵심 규칙 외에는 케이스 바이 케이스라 애매한 지점이 많다. 애매할 때는 아래의 기준점을 참고하면 좋을 것 같다.

1. 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력 최소화에 유리한가?
2. 소스 코드 의존성이 안쪽으로, 고수준의 정책을 향하고 있는가? 
3. 세부 사항이 변경되어도 도메인(핵심 규칙)에 변경이 없을 것인가?
4. 테스트하기 쉬운가?
5. 각각의 아키텍처 원칙들을 잘 지키고 있는가?

 

클린아키텍처는 항상 좋을까?

위에서 말했듯이 전통적인 계층적 아키텍처도 충분히 영속성 계층에 독립적으로 도메인 로직을 유지할 수 있을 것이다. 마찬가지로 클린 아키텍처도 항상 좋을 것은 아니다.

 

외부 계층과 철저하게 분리돼야 하므로 애플리케이션 엔티티에 대한 모델을 각 계층에서 유지보수해야 한다. 따라서 코드의 절대적인 양이 많아질 것이며, 프로젝트 개발자 모두가 클린 아키텍처를 이해하고 있지 않을 때 혹은 모두가 사용하기로 합의하지 않았을 때에는 사용하는 것이 오히려 좋지 않을 수 있다고 생각한다.

 

참고

728x90

+ Recent posts