전체 보기
🚀

클린 아키텍처에 대한 오해와 헥사고날 아키텍처에 대한 무한한 신뢰가 답답해서 쓰는 글

작성일자
2024/09/05
태그
DESIGN
HIGHLIGHT
프로젝트
WoowaCourse
책 종류
1 more property
내 생각이자 이 글에서 말하고 싶은 바를 한 줄로 요약하자면, “클린 아키텍처는 따라야 할 스타일이 아니라, 지향해야 할 규칙이다”라는 점이다. 관련해 하고 싶은 말이 많았어서 글이 조금 길긴 한데, 핵심만 보고 싶은 이들은 중간에 ‘덕질을 하다 답답해진 포인트’부터 읽어도 무방하다.
요즘 내 최대 관심사는 클린 아키텍처다. 오늘만해도 집에 와서 읽은 클린 아키텍처 관련한 책이 너무 설레는 배움을 줘버린 바람에 다른 해야 할일이 눈에 보이지 않게 방해했다. 클린 아키텍처를 모두가 알았으면 좋겠는 이 마음을, 글로 정리해서라도 가라앉혀 보려 한다.

클린 아키텍처를 덕질하게 된 계기

클린 아키텍처에 대한 내 관심(을 넘어선 사랑)은 레벨 3가 끝나고 찾아온 방학부터 본격적으로 시작됐다. 그 전까진 사실 클린 아키텍처에 대해선 모두가 알고 있을 유명한 원 그림 정도만 알고 있었다. 그렇다면 어쩌다 클린 아키텍처에 대한 애정 어린 탐구가 시작된걸까.

데이터베이스 주도 개발 편하고 좋은 거 아닌가요?

레벨 2에 간단한 스프링 어플리케이션을 개발하며 생각했다. “JPA 엔티티를 그대로 도메인으로 쓰는 거 너무 편하고 좋은데?ㅋㅋ”라고 말이다. 그래서 JPA 엔티티가 지키지 못하는 불변성이나 DB와의 종속성을 불편해하는 소수의 크루들을 오히려 신기해했다. 신기해서 해당 크루들이 코치에게 가서 불편함을 토로하는 걸 따라가서 구경도 했다.
앞선 소수의 크루들은 객체지향이 지켜지지 않는 걸 보며 극도의 불안함을 느껴했지만, 나는 기술에 한정해 무한한 신뢰를 오히려 의심하고 경계하는 성격이라 그동안 크루들에게 무한 추종받던 객체지향이 무너지는 걸 보고 약간의 희열을 느끼기도 했다. 그동안 배운 객체지향의 패러다임을 야금야금 깨트리는 JPA 엔티티를 보며 좋아라 했던 것이다.
당시 크루들이 내린 결론의 핵심은 “객체지향도 결국 좋은 코드를 위한 방식 중 하나일 뿐, 객체지향만이 좋은 코드는 아니다”였다. “좋은 코드를 위한 길엔 여러 가지가 있고, 개발에 진정 은탄환은 없구나.”를 느끼며 마무리한 레벨이었다.

데이터베이스 설계는 내 최대 관심사(였던 것)

당시 데이터베이스 주도 개발이 너무 편리하다 생각한 나머지 지금의 내 생각과 반대 방향의 생각을 가지고 아래의 테코톡까지 찍었다. 당시 갖고 있던 지식과 상황 속에선 도메인보다 DB 테이블을 먼저 설계하는 게 편리하다 생각해 DB 테이블을 제대로 설계하는 법에 더 관심을 가졌다. 물론, 클린 아키텍처에 대한 현재 관심에 비하면 매우 작은 관심이었다.
그렇게 막 끌리진 않았지만 당시엔 그게 유일한 설계라 생각했다. 돌이켜보면 오만한 생각이었다. 개발에 은탄환은 없단걸 느끼고서도 하나의 방식만으로 코드를 바라보았던 것이다. 물론 레벨 2에서 경험한 간단한 어플리케이션의 경우 문제가 발생할 만한 특징이 없었기에 틀린 판단은 아니다.
데이터베이스 설계 역시 중요한 부분이기에 의미 있다. 하지만 도메인의 역할이 얼마나 막중하고, DB 테이블도메인을 동일하게 취급할 때 올 수 있는 불편함을 이제는 알아버렸기에 현재 내 관심사는 180도 바뀌었다.

데이터베이스 주도 개발에 불편함을 느끼기 시작(with 총대마켓)

레벨 3에선 “총대마켓”이라는 공동 구매 서비스를 개발했다. 팀원들 모두가 익숙한 방식인 Controller, Service, Repository, Entity 구조로 별 고민 없이 개발을 시작했다. 여기서 EntityJpaEntityDB 테이블이자 우리 서비스의 도메인 역할을 했다. 도메인을 먼저 설계하잔 의견이 나왔지만 쉽지 않았다. 도메인을 조금 설계해보다 생각보다 결과가 잘 안나오자, 우리는 곧장 편하고 쉬운 지름길을 찾아 DB 테이블을 먼저 설계하기 시작했다.
처음엔 문제 없었다. 공동 구매 모집 공고를 저장하는 테이블이 유독 뚱뚱하단 걸 눈치 채기 전까진 말이다. 공동 구매 모집 공고엔 ‘공동 구매할 물품에 대한 정보’, ‘공동 구매를 진행할 일시에 대한 정보’, ‘공동 구매 진행 상태’, ‘공동 구매 참여 인원 정보’가 들어간다. 설계 당시엔 DB 테이블을 기준으로 생각했고, 정규화가 필요없는 데이터들이라 생각해 하나의 테이블에 몽땅 넣었다.
비즈니스 기능이 점점 붙을 수록, ServiceEntity는 무섭도록 뚱뚱해져갔다. 이에 대한 방편으로 공동 구매 모집 공고 테이블을 여러 테이블로 분리할 지를 고민하니 Controller 부터 Entity까지 코드 전역을 건들여야 한단 문제점을 인지할 수 있었다. 비즈니스 기능과 데이터베이스가 강하게 결합되어 있기 때문이다.

총대마켓 팀의 죽어가는 도메인 로직 살리기

이에 총대마켓 팀은 Domain을 분리하기 시작했다. 클린 아키텍처니 DDD니 하는 개념들은 당시 몰랐고, Entity라도 날씬하게 만들어보고자 기교를 부려 기존의 JpaEntity에서 Domain을 만들어 반환하는 구조로 리팩토링했다. 만들고 보니 나쁘지 않았다. 최소한 도메인 로직들은 Domain에 안전하게 들어갔다. Domain은 그 누구에도 의존하지 않기 때문이다. 그렇다면 총대마켓의 문제는 모두 해결됐을까?
ServiceJpaRepository에 의존하고 있기에 모든 비즈니스 로직이 데이터베이스로부터 분리되진 않았다. JpaRepository를 어떻게 잘 감싸면 될 거 같은데 여기부턴 당시 방법이 떠오르지 않았다. 그렇게 데이터베이스와 비즈니스/도메인 로직을 분리하고자 했던 우리의 노력들은 잠시 접어둔 채 방학이 다가왔다.
거기다 방학 직전 포비와의 레벨 인터뷰에서 포비가 Domain 분리에 대해 질문 주셨다. 이때, 아직 해결책이 전부 떠오르지 않은 상태에서 내놓은 내 답변이 내심 스스로 아쉬웠고 답답했다. 명료하게 총대마켓 팀의 근거를 설명하고 싶은데 그 사이사이가 끊어져 있는 느낌이 들었다.

생애 첫 덕질 시작

그렇게 방학이 시작되고 휴식 중 우연히 본 클린 아키텍처와 관련된 유튜브 영상이 기점이 되어 클린 아키텍처에 깊게 매료된 것이다. 클린 아키텍처는 우리 팀이 마주한 문제를 데이터베이스는 세부사항이다! 란 말로 명료하게 설명했고, 해결책에 대한 아이디어도 제시했다.
해결책 궁금하다면 간략히 보기
사실 나는 살면서 연예인이나 게임 등 그 어떤 오락적 요소도 깊게 덕질해본 적이 없다. 오래 하고 있는 게임은 있어도, 그 게임의 캐릭터나 룰을 알기 위해 문서를 모으고 앞뒤 안가리고 덤비진 않았다. 그런데 뜬금없이 클린 아키텍처와 사랑에 빠지다니 조금 어이없다.
요근래 이동 중에도, 집에서도 유튜브로 클린 아키텍처 관련 영상들만 하루종일 봤다. 처음엔 궁금증에서 시작했는데 이젠 너무 재밌어서 심심할 때마다 찾아본다. 클린 아키텍처는 그동안 결정하기 어렵고, 애매하게 다가왔던 내 고민들을 명료하게 해결해주었다. 예를 들어 DTO와 Domain 변환을 어디서 해야 할지에 대한 결정도 클린 아키텍처에서 근거를 얻을 수 있다. 클린 아키텍처의 효율은 정말 말도 안된다고 생각하기에, 클린 아키텍처를 모르는 개발자가 한 명도 없었으면 좋겠다고 생각했다.
뭘 그렇게 재밌게 봤는지 궁금한 사람들을 위한 최근 유튜브 시청 기록

덕질 포인트: 따라야 할 스타일 X, 지향해야 할 규칙 O

영상으로 접하다 자연스레 <클린 아키텍처> 책도 찾아 읽게 되었는데, 워낙 방대한 내용에 압도당해 부분부분 제일 궁금한 부분들만 발췌독했다. 특히 인상 깊었던 부분은 6부. 세부사항 파트다. 데이터베이스, 웹, 프레임워크는 세부사항이란 내용들인데, 저자가 마케팅 담당자와 싸웠던 경험도 들어있어 특히 재밌게 읽었다. 뭐부터 볼지 고민하시는 이에게 매우 추천한다.
글의 도입부에 언급한 설레는 배움을 준 책은 <만들면서 배우는 클린 아키텍처>다. 이 책은 반대로 굉장히 짧고 컴팩트하다. 헥사고날 아키텍처를 구현하는 내용의 책이란 얘기를 어디서 듣고서, 헥사고날을 까기 위해 해당 책을 구매해 읽었다가 까여야 할 건 내 생각이란걸 깨달았다.
책의 저자인 톰 홈버그 씨의 아래 영상도 책 없을 당시 봤을 때 매우 도움이 되었다. 책엔 영상보다 더 디테일한 내용이 담겨있다. 영상을 보고 책을 보니 더 흡입력이 좋아서 책은 거의 1~2시간만에 처음부터 끝까지 다 읽은 거 같다. 그 한 시간 사이에 내게 엄청난 영감을 주었다. 바로 헥사고날만이 정답은 아니란 내용인데, 아래에 더 자세히 서술하겠다.

덕질을 하다 답답해진 포인트

클린 아키텍처는 헷갈리니 헥사고날 아키텍처를 써보자?

시중에 돌아다니는 유튜브 영상 중 몇몇 개를 보며 지나치게 헥사고날 아키텍처를 강조한단 느낌을 받았다. 헥사고날 아케텍처가 나쁘다는 건 절대 아니다. 헥사고날 아키텍처는 분명 클린 아키텍처를 이해하는 데 엄청난 도움을 준다. 클린 아키텍처를 온전히 이해하고 싶다면 그 전신인 헥사고날 아키텍처를 배우고 오는 건 매우 유용하다 생각한다. 실제로 나 역시 도움을 많이 받았다.
클린 아키텍처 자체가 헥사고날, DCI, BCE 같은 수십 년간 발전해온 아키텍처들을 토대로 이들의 공통적인 목표를 뽑아 정리한 규칙 같은 존재이기에, 어쩌면 당연하게도 헥사고날 아키텍처 그림은 클린 아키텍처 그림과 유사하다. 이는 헥사고날을 쓰면 마치 클린 아키텍처를 완벽하게 적용할 수 있을 것만 같은 느낌을 준다. 과연 그럴까?
헥사고날 아키텍처
클린 아키텍처
개인적으로 헥사고날을 무작정 그대로 따라해 프로젝트에 적용하는 건 반대다. 팀이 겪는 문제를 인지하고, 어디까지가 왜 팀에서 필요한지를 잘 판단해야 한다. 헥사고날에서 등장하는 웹 어댑터, 입력 포트, 유스케이스, 출력 포트, 영속성 어댑터를 정말 쉽게 풀어내보자면 아래와 같다.(실제 이름은 저마다 다르게 붙일 수 있다.) 클린 아키텍처에서 해당하는 레이어의 색깔까지 지정해주었다.
WebAdaptor : Controller
InputPort : Service 인터페이스
UseCase : Service 구현체
Entity : DomainEntity
OutputPort : RepositoryAdaptor 인터페이스
PersistenceAdaptor: RepositoryAdaptor 구현체 (JpaRepository를 이용해 JpaEntity 다루는 역할)
내가 가장 이질감을 느꼈던 부분은 Service 인터페이스였다. RepositoryAdaptor를 인터페이스화해야 한단 건 동의한다. 총대마켓 프로젝트에서도 ServiceRepository를 의존해 문제였기 때문이다. 이럴 땐 의존성 역전 원칙을 적용해 의존성의 방향을 바꾸는 선택이 합리적이다. Controller → Service → Repository 구조에서 Controller → Service ← Repository 구조로 변경해 Service를 외부(Web, DB)에 의존하지 않게 만들어 줄 수 있다. 따라서, OutputPortPersistenceAdaptor 사이의 implements 관계는 충분히 납득간다.
그렇다면 UseCase(Service 구현체)InputPort(Service 인터페이스) 사이의 implements 관계는 왜 필요한걸까? Service를 인터페이스화하지 않으면 추상화 계층을 줄일 수 있고, 보통 추상화 계층을 줄이는 것은 괜찮게 느껴진다. 추상화 관련해선 정말 인상 깊게 본 글이 있어서 아래 남긴다.
헥사고날 측에선 내가 쉽게 풀어 말했던 Service의 인터페이스를 어려운 용어로 인커밍 포트라고 부른다. 톰 홈버그씨의 책에 따르면 인커밍 포트가 필요한 이유는 첫째, 애플리케이션 중심(클린아키텍처에서 붉은색과 노란색 부분을 의미)에 접근하는 진입점을 정의할 수 있어서 라고 말한다. 인커밍 포트를 제거하면 어떤 기능을 구현하기 위해 어떤 서비스 메서드를 호출해야 할지 알기 어려워 질 것이라 말한다. 솔직히 이 이유는 내가 아직 겪지 못해서 그런 건지 공감 가진 않는다.(의견 환영)
두 번째로, 아키텍처를 쉽게 강제할 수 있단 것도 인커밍 포트가 필요한 이유로 들고 있다. 이 부분은 어느 정도 납득 간다. 실제로 톰 홈버그씨 책의 10장에 있는 아키텍처 경계 강제하기 부분은 꽤나 흥미롭다. Gradle 같은 빌드 도구를 이용해 계층 간 의존성을 강제할 수 있단 내용이 나와있다. 이 부분 역시 실제로 사용해보진 않아서 잘은 모르겠지만, 첫 번째 이유와 달리 논리적으론 납득이 갔다.
헥사고날을 그대로 따라하기 전에, 헥사고날이 왜 Service 인터페이스화를 사용하는지를 꼭 이해해야 한다. 우리 팀 기준으로 Service 인터페이스화가 필요한 이유들은 현재 문제가 되지 않고 있는 부분이기도 하고, 크게 중요치 않은 부분이다. 그렇다면 적용할 필요 없다. 총대마켓 팀은 아래와 같은 구조를 가져갈 것이다.
Controller
Service
DomainEntity
RepositoryAdaptor 인터페이스
RepositoryAdaptor 구현체 (JpaRepository를 이용해 JpaEntity 다루는 역할)
엄밀히 말하면 헥사고날은 아니다. 특이한 구조지만, 클린 아키텍처는 맞다. 우리는 기술에 종속되지 않고, 규칙에 기반하는 클린 아키텍처를 따라야 한다. 실제로 톰 홈버그씨의 책에선 헥사고날을 그대로 도입해야지만 클린 아키텍처를 할 수 있단 게 아니란 점을 강조한다. 톰 홈버그씨의 책을 물론 사람마다 다르게 해석할 수도 있지만, 적어도 내겐 그렇게 해석됐다.

계층형 아키텍처는 클린 아키텍처가 될 수 없다?

이 부분은 톰 홈버그씨의 의견에 100% 동의하기에 그의 책에서 가장 적절한 인용구를 가져와보았다.
올바르게 구축하고 몇 가지 추가적인 규칙들을 적용하면 계층형 아키텍처는 유지보수하기 매우 쉬워지며 코드를 쉽게 변경하거나 추가할 수 있게 된다. 그러나 앞에서 봤듯이 계층형 아키텍처는 많은 것들이 잘못된 방향으로 흘러가도록 용인한다. 아주 엄격한 자기 훈련 없이는 시간이 지날수록 품질이 저하되고 유지보수하기가 어려워지기 쉽다. 그리고 이러한 자기 훈련은 보통 새로운 마감일을 설정할 때마다 조금씩 느슨해지기 마련이다. 계층형 아키텍처로 만들든 다른 아키텍처 스타일로 만들든, 계층형 아키텍처의 함정을 염두에 두면 지름길을 택하지 않고 유지보수하기에 더 쉬운 솔루션을 만드는 데 도움이 될 것이다
위 인용구도 다르게 해석하는 이들이 있을 수 있지만, 적어도 내겐 계층형 아키텍처도 규칙을 잘 적용하면 클린 아키텍처가 될 수 있단 말로 해석됐다.(의견 환영) 예를 들어, 현재 리팩토링 중인 총대마켓의 구조는 헥사고날 아키텍처는 아니다. 오히려 계층형에 가깝지만, 엄밀히 따지면 그것조차도 아니다. 중요한 것은 특정 아키텍처의 형태에 얽매이지 않고, 클린 아키텍처의 핵심 원칙을 지키는 것이다. 계층형이든 헥사고날이든 그 틀에 맞추려는 고정관념을 버리자. 클린 아키텍처의 원칙에 충실해지려 노력한 총대마켓의 구조는 아래와 같다.
Controller
Service
DomainEntity
RepositoryAdaptor 인터페이스
RepositoryAdaptor 구현체 (JpaRepository를 이용해 JpaEntity 다루는 역할)
왜 클린 아키텍처에 부합한다는 걸까? 클린 아키텍처에 입각해서 봤을 때 일반적인 계층형 아키텍처가 유도할 수 있는 문제는 아래와 같다. 최대한 쉽게 풀어 써봤다. 꼭 계층형이 아니더라도 계층형처럼 보이는 구조에서도 해당 문제를 염두해 두면 도움이 된다.
1.
계층형 아키텍처는 데이터베이스 주도 설계를 유도한다.
웹 계층이 도메인 게층에 의존하고, 도메인 계층이 영속성 계층에 의존하기 때문에 자연스레 데이터베이스에 의존하게 된다.
2.
지름길을 택하기 쉬워진다.
계층형 아키텍처는 본인과 같거나 아래에 있는 계층만 사용할 수 있단 제약 하나만 존재하기에, 만일 본인보다 위에 있는 계층의 컴포넌트에 접근해야 할 때 해당 컴포넌트를 util, helper란 명명하에 아랫 계층으로 내려버릴 수 있단 지름길이 존재한다. 이로 인해 아랫 계층인 영속성 계층이 비대해질 수 있다.
3.
테스트하기 어려워진다.
웹 계층에서 영속성 계층을 바로 사용하는 것처럼 한 계층을 뛰어넘어 구현해버리는 지름길이 존재한다. 이로 인해 단위 테스트의 복잡도가 올라간다.
4.
유스케이스를 숨긴다.
서비스에 여러 개의 유스케이스들이 들어가는데, 이로 인해 아주 넓은 서비스가 만들어지기도 한다.
5.
동시 작업이 어려워진다.
한 명이 웹 계층에, 다른 한 명은 서비스 게층에, 나머지 한 명은 영속성 계층에 기능 추가하는 게 불가능하다. 모든 것이 영속성 계층 위에 만들어지기 때문에 영속성 계층을 먼저 개발해야 하고, 그 다음에 서비스 계층을, 그리고 마지막으로 웹 계층을 만들어야 한다.
총대마켓의 구조를 다시 봐보고 문제를 어떻게 해결하고 있는지 살펴보자. 계층형 아키텍처 구조도 총대마켓처럼 조금 손을 봐서 클린 아키텍처를 지향할 수 있다.
첫 번째 문제는 의존성 역전을 이용해 명확히 해결했다.
두 번째 문제는 실제로 persistence 레이어의 util이 많아졌을 때 고민해봐야 할 문제라 생각한다. 현재로선 팀원들이 다같이 인지하고 넘어가도 좋다.
세 번째와 네 번째 역시 두 번째와 마찬가지로 후에 마주했을 때 고민해볼 문제라 생각한다.
다섯 번째는 사실 현재 총대마켓이 겪고 있는 문제긴 하다. 한 사람이 하나의 API를 맡는 형태로 업무를 분담 중인데 서비스나 영속성 계층을 함께 사용할 때 충돌이 많이 난다. 리팩토링 후엔 최소한 영속성 계층에서만큼은 문제가 되지 않으리라 기대한다. 어쩌면 이 부분도 추후 서비스 인터페이스 도입의 이유가 될 수도 있으리라 기대한다. 실제 체감해보며 그 효과가 크다면 도입 고려해볼 거 같다.

클린 아키텍처는 어렵고 복잡하다?

클린 아키텍처는 지향해야 할 규칙이다. 그리고 그 핵심 규칙은 의존성 규칙이다. 의존성 규칙을 더 설명하기 위해 클린 아키텍처는 엔티티, 유스케이스, 인터페이스 어댑터, 경계 횡단 등 이것저것을 제시한다. 하지만 이들 모두 결국엔 의존성 규칙을 지키기 위한 부수 설명들일 뿐, 하나가 빠진다고 클린 아키텍처에서 탈락되진 않는다.
의존성 규칙: 소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다
그러므로 클린 아키텍처를 어렵게 생각하지 말고, 그 핵심 규칙을 명심하며 지향하도록 노력하면 좋겠다. 사실 나도 과거엔 클린 아키텍처의 원 그림에서 용어들을 보고 이해하지 못하리라 생각하고 넘겼었다. 하지만, 우연찮게 학습해보니 절대 어렵고 복잡한 내용이 아님을 알 수 있었다.
우리가 평소 중요하게 여기던 단일 책임 원칙의존성 역전 원칙 두 가지를 강조하는 게 클린 아키텍처다. 클린 아키텍처에서 설명하는 바를 이해하고 코드에 응용하려 노력하면 좋은 품질의 코드를 만들 수 있다. 비단, 프로젝트의 패키지 구조를 전부 갈아 엎는 게 아니더라도 일부 코드에 적용할 수 있는 좋은 아이디어를 많이 얻을 수 있을 것이다. 그러므로 아직 클린 아키텍처를 접해보지 못한 운 좋은 개발자가 이 글을 봤다면 꼭 이 기회에 학습해보길 권장한다.

Comming Soon: Uncle Bob처럼 생각해 코드 개선의 근거 얻기

Uncle Bob의 The Clean Code Blog에 가면 Clean Architecture 에서 중요하게 생각하는 규칙들이 명시되어 있다. 해당 규칙들에 바탕해 생각했을 때, 실제 코드에서 판단 근거가 될만한 요소들은 다음 포스팅에서 정리해보겠다. 이 글의 중간쯤 언급한 DTO 사용법 같은 사소할 수도 있는 내용들부터 총대마켓에서 리팩토링하고 있는 도메인 구조까지 언급할 예정이다.