CH01 계층형 아키텍처의 문제는 무엇일까?
Last updated
Last updated
The most common architecture pattern is the layered architecture pattern, otherwise known as the n-tier architecture pattern.
책에서 시작부터 계층형 아키텍처를 안좋게 이야기하기 때문에 계층형 아키텍처가 뭔지에 대해서 짚고 넘어간다. 위에 설명이 잘 나와있는데 요점만 뽑아 아래와 같이 정리할 수 있을 것 같다.
레이어로 구성되어 있고 각 레이어는 각자의 role 이 분명하게 존재한다.
거의 전형적으로 위와 같은 레이어 형태(프레젠테이션, 비즈니스, 영속성, 데이터베이스)로 존재하며 의존 방향이 결국 아래 레이어(데이터베이스)로 향한다.
책에서 말하고자 하는 바는 계층형 아키텍처가 완전히 잘못되었다는 말이 아니고 나쁜 코드로 될 여지가 계속 의도치 않게 열려있는 설계라는 것이다.
'잘 만들어진 계층형 아키텍처도 말 그대로 '잘 만들면' 괜찮은거 아니야?'라는 반감을 약간 가지고 책을 봤는데 수긍하게 되었다. 계층형 아키텍처(적어도 내가 만들고, 회사에서 겪은)는 데이터베이스 드리븐된다.
책에서 말하는 핵심 이유는 우리가 ORM 을 쓰기 때문이라는 것이다. 결국 객체를 설계할 때 아무리 행동 중심으로 사고하고 도메인 중심으로 사고하려 해도 궁극적으로 데이터베이스에 들어가는 형태를 고려하게 되면서 자연스럽게 데이터베이스가 개발자를 가르치는(?) 형태가 된다. 현실의 표상으로 객체를 설계하는게 아니고 컬럼명에 맞춰서 객체를 설계하고 로직을 구성하게 된다.
객체 지향의 사실과 오해에서도 강조 되었듯이 좋은 설계는 상태가 아니라 행동을 중심으로한 사고에서 나올 수 있다고 생각한다. 하지만 계층형 아키텍처에서는 ORM 을 사용하면서 궁극적으로 영속성 계층에의 종속성을 떨칠 수 없기 때문에 데이터베이스에 대한 고려가 설계의 근간에 자리하고 있다. 여기서 데이터베이스 주도 설계가 발생하게 된다.
그렇다. 데이터베이스 주도 설계란 데이터베이스가 시스템의 설계에서 많은 영향력을 발휘하는 것인데 데이터베이스에 대해서 도메인 로직이 종속되어 있으면 계속 변경이 발생하는 어플리케이션이 수정 비용이 많이 들게 된다.(뭐 하나 바꾸려면 컬럼을 바꿔야 한다던가 테이블 구조를 바꿔야 한다던가)
계층형 아키텍처로 구현한 나의 코드 및 다른 사람들이 짠 코드를 보아도 결국 영속성 계층에 있는 @Entity 에 비즈니스 로직이 많이 들어가게 되는데 엄격하게 보면 도메인 로직만을 다뤄야 하는 객체에 레이지 로딩 등 JPA 관련 코드가 섞여버린다. 즉, 기술에 종속성이 발생한다. 기술에 대한 종속성 없이 POJO 만으로 비즈니스 로직만을 다룰 수 있는 상태가 이상적인데 계층형 아키텍처에서는 거의 이렇게 하지 않고 JPA 기술을 녹인 엔티티 객체에서 비즈니스 로직까지 같이 다루고 있다.
계층형 아키텍처에서는 윗 레이어가 아래 레이어를 의존한다. A->B->C 인 것이다. 그런데 만약 결론적으로 C 의 특정 로직을 사용해야하는데 B에서 별다른 행동이 필요하지 않다면? A->C 도 가능할 것이다. 하지만 이건 계층형 아키텍처에서 레이어간 구분을 일부러 해뒀는데 지름길을 택하는 것이다.
결론적으로 지름길을 택하게 되면(유혹에 의해) 계층의 구분이 모호해지고 하나의 계층이 비대해지는 현상이 발생할 수 있다.
책에서는 이걸 방지하는 방법으로 '시니어의 코드리뷰'가 아니라 아예 빌드 자체가 실패하도록 구조를 짜는것을 말하고 있다.
앞서 말한 '지름길을 택하기 쉬워진다'에서 이어지는 맥락인데 계층간 구분이 모호해지면서 테스트가 어려워진다. 왜냐하면 시스템 전반에 책임이 섞이게 되기 때문이다. 레이어별로 책임이 명확해야 모킹도 쉬워지고 테스트가 간결해지는데 레어어가 맡은 책임의 경계가 모호할 수록 테스트 비용이 높아진다.
레거시 코드를 개선하거나 레거시 프로젝트에 기능을 추가할 때 가장 시간이 많이 걸리는 부분중 하나는 '어디에 뭐가 있는지' 탐색하는 것이다. 특정 기능을 수행하는 위치를 찾는 데만 많은 시간이 걸린다.
예를 들어 사용자 탈퇴와 관련된 로직을 찾는다고 했을때, 뚱뚱한 UserService 를 통으로 샅샅이 다 보고 이걸 담당하는 로직을 찾은 다음 서비스 내 로직 혹은 엔티티 내에서 다루고 있는 로직을 봐야 한다. 특히 이게 계층간 경계가 모호해지고 비즈니스 로직이 레이어 별로 조금씩 분산 되어 있으면 더 찾기도 어렵고, 수정도 어렵다.
이 부분에 관해서 책의 표현을 빌리자면 서비스의 '너비'에 관한 규칙을 계층형 아키텍처는 강제하지 않는다는 것이다.
이 부분이 개인적으로 헥사고날 아키텍처가 적용 되었을 때의 장점으로 가장 크게 느껴졌다. 만약 처음 접하는 레거시 프로젝트가 헥사고날 아키텍처로 설계되어 있으면 유스케이스 위주로 기능을 확인할 수 있다.
이건 책의 내용이 절반만 동의가 되었는데, 헥사고날을 적용해도 어차피 핵심 엔티티는 함께 써야하기 때문에 그것에 대한 합의가 되기 전에는 작업자들끼리 병렬적으로 일을 진행하긴 힘들다.
다만 위와 같이 서비스가 넓은 상태에서 3명의 개발자가 협업을 한다고 했을때, 각자 다른 기능을 개발한다고 해도 각자 넓은 서비스, 영속성 레이어 등에 작업을 하게 될텐데 코드 충돌이 날 여지가 매우 크다고 할 수 있다.
그럼 이게 헥사고날을 적용한다고 좋아지나? 그렇다.
왜냐하면 유스케이스로 3명의 개발자가 각자가 개발하는 기능을 아예 구분할 것이라서 코드 충돌이 날 일이 없다. 그리고 구현체를 만드는 것도 각자 할 것이니까 마찬가지로 충돌 날 일도 없고, 심지어 영속성과 관련해서도 어댑터를 따로 구현한다면 코드 충돌이 일어날 여지가 없다.
핵심은 철저하게 설계 단계부터 기능적으로 나뉘고 도메인 로직은 기술로부터 완전히 격리시키는 것이기 때문에 이걸 생각하면 동시작업이 계층형 아키텍처보다 헥사고날 아키텍처가 더 쉽다고 이해할 수 있다.
Components within the layered architecture pattern are organized into horizontal layers, each layer performing a specific role within the application (e.g., presentation logic or business logic). Although the layered architecture pattern does not specify the number and types of layers that must exist in the pattern, most layered architectures consist of four standard layers: presentation, business, persistence, and database ().