티스토리 뷰
이 포스팅은 만들면서 배우는 클린 아키텍처를 읽고 작성하였습니다.
개요
계층으로 구성된 전통적인 웹 애플리케이션 구조는 보통 아래 그림과 같습니다.
웹 계층에서는 요청을 받아 도메인(또는 비즈니스) 계층에 있는 서비스로 요청을 전달하고, 서비스에서 비즈니스 로직을 수행한 뒤, 도메인 Entity의 상태를 조회하거나 변경하기 위해 영속성 계층의 컴포넌트를 호출합니다.
계층형 아키텍처는 견고하고, 잘 이해한다면 독립적으로 도메인 로직을 작성할 수 있으며, 도메인 로직에 영향을 주지 않고 웹 계층과 영속성 계층을 변경할 수 있습니다.
잘 만들어진 아키텍처는 요구사항의 변화에 유연하고 외부 요인에 빠르게 적응할 수 있게 해줍니다. 반면 계층형 아키텍처는 코드에 나쁜 습관이 생기기 쉽고 시간이 지날수록 점점 더 변경하기 어렵게 됩니다.
지금부터 그 이유를 알아보겠습니다.
데이터베이스 주도 설계 유도
전통적인 계층형 아키텍처의 토대는 데이터베이스이고, 각 계층은 순차적으로 의존하다 최종적으로 영속성 계층에 의존하게 되기 때문에 자연스럽게 데이터베이스에 의존성이 생깁니다. 이 말은 곧 모든 것이 영속성 계층을 토대로 만들어진다는 것과 같습니다.
애플리케이션의 목적은 보통 규칙과 정책을 반영한 모델을 만들고 사용자가 보다 쉽고 편리하게 규칙과 정책을 활용할 수 있게 하는 것입니다. 우리는 상태가 아니라 행동을 중심으로 모델링하게 되는데, 행동이 상태를 바꾸는 주체이기 때문에 행동이 비즈니스를 이끌어가게 됩니다. 그럼에도 불구하고 도메인 로직이 아닌 데이터베이스를 토대로 아키텍처를 구성하는 이유는 무엇일까요?
보통 애플리케이션을 개발할 때를 떠올리면 생각보다 간단하게 이유를 찾을 수 있습니다. 아마 대부분 도메인 로직을 먼저 구현하기보단 데이터베이스 구조(테이블 스키마 등)를 먼저 생각하고, 그를 토대로 도메인 로직을 구현할 것입니다.
이는 전통적인 계층형 아키텍처에서 합리적인 방법입니다. 의존성 방향에 따라 자연스럽게 구현하다보면 위의 순서가 되기 때문입니다. 하지만 비즈니스 관점에서는 전혀 맞지 않는 방법입니다. 도메인 로직을 먼저 설계하고, 도메인 로직이 맞다는 것을 확인한 뒤에 영속성 계층과 웹 계층을 만들어야 합니다.
데이터베이스 중심적인 아키텍처가 만들어지는 가장 큰 원인은 ORM 프레임워크를 사용하기 때문입니다. ORM 프레임워크 자체의 문제가 아니라 계층형 아키텍처 내에서 사용하다보면 비즈니스 규칙을 영속성 관점과 섞어서 생각하게 되기 때문입니다.
일반적으로 ORM에 의해 관리되는 Entity들은 영속성 계층에 위치하고 도메인 계층에서 Entity를 접근할 수 있고, 접근할 수 있다면 분명 사용하게 됩니다. 이렇게되면 도메인 계층과 영속성 계층간의 강한 결합을 유발하게 됩니다. 서비스는 영속성 계층의 모델을 비즈니스 모델처럼 사용하게되고, 이로 인해 도메인 로직뿐만 아니라 영속성 계층과 관련된 작업들(로딩, 트랜잭션, 플러시 등)을 해줘야 합니다.
영속성 코드가 도메인 코드에 녹아들어가서 둘 중 하나만 바꾸는 것이 어려워지게되고, 이는 유연함을 목표로하는 아키텍처의 목표와 반대되는 상황이 됩니다.
지름길을 택하기 쉬움
전통적인 계층형 아키텍처의 유일한 규칙은 특정 계층에서 같은 계층에 있는 컴포넌트 또는 하위 계층에만 접근 가능하다는 것입니다. 보통 개발을 하다보면 회사 내에서, 팀 내에서 합의한 다른 규칙들이 여기 추가됩니다. 상황에 따라 상위 계층 컴포넌트에 접근해야 한다면 해당 컴포넌트를 하위 계층으로 이동시키면 간단히 해결되지만 한 번이 두 번이 되고, 나중엔 원래 어떤 규칙이 있었는지조차 잊어버리게 됩니다.
보통 이런 지름길을 택할 때 개인이 결정을 내리는 경우는 드뭅니다. 대부분 상사나 동료들과 함께 어쩔 수 없는 예외상황에 대해 해결책을 찾다가 돌아올 수 없는 강을 건너게 되고, 다음 번에는 이전이 레퍼런스가 되어서 죄책감마저 줄어들게 됩니다. 이를 깨진 창문 이론이라고 부릅니다.
영속성 계층을 유지보수하다보면 위 그림과 같아질 확률이 매우 높습니다. (전 이 부분에서 소오름🐂) 한번 허용한 컴포넌트가 영속성 계층으로 내려오기 시작하면 영속성 계층이 비대해지는 것은 시간문제입니다. 보통 Helper
나 Utility
가 영속성 계층으로 내릴 가능성이 가장 큰 후보입니다.
테스트가 어려움
계층형 아키텍처를 사용할 때 일반적으로 나타나는 변화의 형태는 계층을 건너뛰는 것입니다. Entity를 바로 조회하거나 하나만 조작하면 되는 경우 웹 계층에서 바로 영속성 계층을 접근하는 경우입니다.
제가 예전에 봤던 강의에서는 일부러 Controller에 Repository를 주입해서 한번에 조회해서 응답을 해버리는 것도 봤었는데요, 이런 예외가 하나, 둘 허용되면 다음 두 가지 문제가 발생합니다.
첫 번째는 하나의 필드를 조작하는 것에 불과하더라도 도메인 로직을 웹 계층에서 구현하게 됩니다. 앞으로 유스케이스가 확장된다면 더 많은 도메인 로직을 웹 계층에 추가하여 전반적으로 계층간 책임이 섞이게되고, 핵심 도메인 로직들이 여기저기 흩어질 확률이 높아집니다.
두 번째 문제는 웹 계층 테스트에서 도메인 계층 뿐만 아니라 영속성 계층도 모킹해야한다는 것입니다. 이렇게 되면 단위 테스트의 복잡도가 올라가고 테스트를 하기위한 설정이 복잡해지면 테스트를 작성하지 않는 방향으로 흘러가게 됩니다. (소오름2🐄)
규모가 커지면 다양한 영속성 컴포넌트에 의존성이 쌓이면서 테스트의 복잡도가 올라가고, 어느 순간에는 테스트 코드보다 모킹하는데 시간을 훨씬 더 많이 사용하게 됩니다.
유스케이스를 숨김
개발자들은 새로운 유스케이스를 새로운 코드로 구현하는 것을 선호하지만 실제로는 기존 코드를 바꾸는 데 시간을 더 많이 쓰게 됩니다. 레거시 프로젝트를 맡아야만 그런 것이 아니라 신규 프로젝트에서도 마찬가지입니다. 기능을 추가하거나 변경할 적절한 위치를 찾는 일이 빈번하기 때문에 아키텍처가 그것을 빠르게 할 수 있게 도움이 되어야 합니다.
계층형 아키텍처는 도메인 로직이 여러 계층에 걸쳐 흩어지기 쉬우므로 새로운 기능을 추가할 적당할 위치를 찾는 것은 이미 어려워졌다고 볼 수 있습니다. 그리고 계층형 아키텍처에서는 도메인 서비스의 범위에 관한 규칙을 강제하지 않아 서비스 계층이 엄청 비대해지기 쉽습니다.
넓고 비대해진 서비스는 영속성 계층과 많은 의존관계를 맺게되고, 웹 계층이 이 서비스에 의존하게 되어, 작업이나 테스트를 위해 유스케이스를 책임지는 서비스를 찾기가 어려워지게 됩니다.
동시 작업이 어려워짐
계층형 아키텍처는 동시작업 측면에서는 그다지 도움이 되지 않는 구조입니다. 애플리케이션에 새로운 유스케이스를 추가해야할 때 세 명이 동시에 작업한다고 가정해봅시다. 한 명은 웹 계층에 필요한 기능을 추가하고, 다른 한 명은 도메인, 나머지 한 명은 영속성 계층에 기능을 추가한다고하면 이게 맞는 방법일까요?
계층형 아키텍처로 개발해보신 분들은 알고계시지만 보통 이렇게 작업하지 않습니다. 모든 것이 최종적으로 영속성 계층을 의존하기 때문에 영속성 계층을 먼저 개발해야하고, 그 다음은 도메인 계층을, 마지막으로 웹 계층을 만들어야 합니다. 따라서 한 명이 개발하는 것보다 세 명이 개발한다고해서 더 낫다고 볼 수가 없습니다.
인터페이스를 먼저 같이 정의하고, 다른 계층 개발이 끝나길 기다릴 필요가 없지 않냐고 반문할 수 있습니다. 당연히 가능하지만 데이터베이스 주도 설계를 하지 않는 경우에 가능합니다. 앞에서도 다뤘지만 영속성 로직과 도메인 로직이 뒤섞여 있는 상태에서 각 계층을 개별적으로 개발한다는 것은 생각보다 어려운 일입니다.
유스케이스를 숨기는 케이스에서 처럼, 서비스가 비대해진 경우엔 더더욱 동시 작업이 어렵습니다. 서로 다른 유스케이스를 추가해야하는 경우, 같은 서비스를 동시에 편집해야하는 상황이 벌어지기 때문에 필연적으로 merge conflict가 발생할 것입니다.
유지보수 가능한 소프트웨어를 만드려면?
계층형 아키텍처를 이용해 개발해보신 분들은 앞서 다뤘던 문제점들에 대해 가슴깊이 공감하실 것입니다. 심지어 여기서 다루지 않은 단점들을 겪어 보신 분들도 많으실 겁니다.
사실 올바르게 구축하고 몇 가지 추가적인 규칙들을 적용하면 계층형 아키텍처는 유지보수하기 매우 쉬워지고 코드를 쉽게 변경하거나 추가할 수 있게 됩니다. 하지만 앞에서 살펴봤듯이 잘못된 방향으로 흘러가도록 방치하고있고, 시간이 지날수록 품질이 저하되어 유지보수가 어려워집니다.
어떤 아키텍처로 구현하더라도 계층형 아키텍처의 함정을 염두해두고 지름길을 택하지 않는다면 유지보수하기 쉬운 솔루션을 만드는데 도움이 될 것입니다.
다음 포스팅에선 계층형 아키텍처의 대안에 대해 다룰 예정입니다.
'Architecture' 카테고리의 다른 글
클린 아키텍처: 영속성 어댑터 구현 (0) | 2022.08.10 |
---|---|
클린 아키텍처: 웹 어댑터 구현 (0) | 2022.08.08 |
클린 아키텍처: 유스케이스 구현 (0) | 2022.08.07 |
클린 아키텍처: 패키지 구성 (0) | 2022.08.03 |
클린 아키텍처: 의존성 역전하기 (0) | 2022.07.28 |
- Total
- Today
- Yesterday
- 헥사고날 아키텍처
- JPA
- 스프링 부트 애플리케이션
- proto3
- 스프링부트
- 스프링 부트
- Spring Data JPA
- Spring Boot
- QueryDSL
- 알고리즘
- 클린 아키텍처
- 함께 자라기 후기
- spring boot application
- JSON
- Java
- spring boot jwt
- Spring Boot Tutorial
- leetcode
- Jackson
- gRPC
- 스프링 부트 튜토리얼
- intellij
- 스프링 부트 회원 가입
- 스프링 데이터 jpa
- 함께 자라기
- r
- Linux
- @ManyToOne
- Spring Boot JPA
- spring boot app
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |