티스토리 뷰
2~3가지 서비스를 거치면서 개발했던 방식은 Layered 아키텍처를 벗어난 적이 없다.
6년짜리 레거시 코드를 Layered 아키텍처로 운영/개발할 때도 큰 불편함을 느끼지 못 했었다.
당시를 떠올려보면 그렇게 생각했던 이유가 몇 가지 있는데..
1. 테스트를 구현하지 않았음
2. 불행인지 다행인지 레거시 코드를 수정할 일이 거의 없었음
3. 서비스의 규모와 팀의 규모가 크지 않았음
4. R&R을 나누는 직급이 아니었음
이 정도가 아닐까 싶다.
그러나 이직 후 업무 분담도 하는 직급이 됐고, 서비스 규모가 점점 커지면서 운영 개발 건이 늘어나다보니 Layered 아키텍처의 문제를 느끼게 됐다.
(내가 느꼈던) Layered 아키텍처의 문제
1. Service가 너무 커져서 파생되는 문제들
- 어떤 기능을 추가하면 Service에만 추가하면서 Service의 역할이 계속해서 커짐
- 너무 커진 Service가 부담스러워서 코드를 분리한다고, 비슷한 Service를 만들면서 비슷한 코드가 혼재하게 됨
- 그에 따른 한눈에 들어오지 않는 Service의 역할(어떤 컨트롤러에서 쓰이고 어느 서비스에서 이 매서드를 불러가나?)
- 또 그에 따른 종종 발생하는 순환 참조 문제
- 커진 Service를 테스트 하기 위해서는 엄청난 양의 Mock 객체가 필요해짐
역할 분리를 엄격히 잘해뒀다면, 코드의 길이는 문제가 되지 않겠지만 Layered 아키텍처에서는 나누기가 어려웠다.
2. R&R을 나누기 어려움
- 객체든 사람이든 기능보다는 데이터에 의존해서 역할을 나누게 됨 -> 적절한 크기로 작업이 분배가 안됨
- 한 Service에서 여러명이 작업하고 Merge하면, 모든 사람이 무수한 Conflict와 마주하게 됨
위와 같은 문제가 발생하기 시작했다.
무엇보다 테스트 작성을 해야하는데, 테스트를 하는건지 Mocking을 하는건지 모를 정도로 많은 Mock 객체가 들어와서 난감했다.
그래서 테스트가 자주 누락되는 현상이 생기게 되면서
모든 테스트는 아니더라도 누가봐도 헷갈리는 부분은 테스트가 반드시 필요한데 그것조차 없는 경우가 생겼다.
일단, 테스트라도 쉽게 만들어보자는 생각에 한 부분을 잡고 대대적인 리팩토링에 들어갔다.
(일부 상용서비스 중인 부분이 있어서 안전을 위해 통합테스트는 모두 만들어놓고 시작했다)
CRUD 위주의 API 20개 남짓한 프로젝트를 잡고 리팩토링을 진행했고, 빠르게 결과를 볼 수 있었다.
Hexagonal 아키텍처로 변환 후 느낀점들
느낀점 1. 생각보다 쉽지 않았던 Port-Adapter 구조로의 변환
인터페이스를 기반으로 개발하는게 익숙하지 않다면, 이 부분이 가장 큰 러닝 커브를 만드는 요소다.
도메인을 유지하면서 의존성을 외부에서 주입받는 구조를 만든다. 주입받는 방식은 자유롭게 선택한다.
이 개념은 초기 구축하는 사람이 아니고선, 느끼기 어려울 수도 있겠다는 생각이 들었다.
그리고 일반적인 서비스에서의 DB는 사실상 바꿀 일이 없기 때문에, OutputPort를 굳이 분리해야하나란 생각이 들었다.
MySQL만이 아니라 여러 DB를 쓴다면? 이라는 가정은 작은 시스템에선 하기 어렵다.
느낀점 2. 굳이..? 라고 생각했던 영속성 Port와 과하게 분리되는 Usecase들
Spring Data JPA + QueryDSL로 영속성 계층을 구성했다면, 이미 추상화된 부분들이 많다.
그러다보니 영속성 Port를 구성할 때, Spring Data JPA로 구성하면 굳이 만들지 않아도될 중복 코드가 많이 생긴다.
다른 영속성 라이브러리나 DB를 쓰는 부분이 많다면, 고려해야할 부분이 있겠지만 지금은 써봐야 Redis 정도다.
그래서 영속성 Port의 유지는 생각을 해봐야할 문제 같다. 테스트가 문제 없다면 과감히 삭제할 수도 있을 것 같다.
마지막으로, Usecase들은 팀 컨벤션에 따라 어느정도 묶어두는걸 허용하는게 좋을 것 같다.
아직 메인 서비스에 적용 전이지만, 중복 파일이 많이 생길게 눈에 보인다.
느낀점 3. 계층 간 이동할 때 만들어지는 변환 객체들
클린 아키텍처에서는 각 계층을 이동할 때 별도의 모델을 갖기를 권장한다.
그러다보면 요청이 들어와서 도메인 객체로 변환될 때까지 크게 변하지 않는 객체들이 모든 계층에서 만들어지게 된다.
이 부분은 팀에서의 적절할 합의가 필요한 부분인 것 같다.
느낀점 4. R&R은 Usecase로 나누면 됨
데이터로 R&R을 나누게되면 한 사람이 맡아야하는 작업의 비중이 커지는데, 이 부분을 쉽게 해결할 수 있을 것 같다.
그리고 Service가 implements한 Usecase들만 확인하면 서비스의 역할도 쉽게 확인할 수 있다.
에러 분석할 때 더이상 컨트롤러와 여러 서비스들을 방황할 필요가 없어졌다.
느낀점 5. 반드시 도입해야하는가? No
여러가지 이유가 있을 수 있다. 일단은 우리 조직에 적합하지 않다는 생각이 들었다.
첫번째는 생산성이 높아지기까지의 러닝 커브의 장벽은 생각보다 높다.
이런 아키텍처를 생판 처음보는 조직원이 따라오기 어려울 수 있다.
두번째는 input 포트와 output 포트가 확장될 가능성이 낮고, 고정되어 있는 상태에서는 굳이 도입할 필요가 없을 것 같다.
멀티모듈, 서버투서버, 큐 등의 다양한 인프라가 적용되지 않은 프로젝트에서는 코드의 복잡성만 증가시키리라 예상된다.
마지막으로는 MVP 개발이 잦은 우리 조직에서는 어울리지 않을 것 같다.
빠른 개발엔 단순한 레이어드 아키텍처가 최고다...
그래서 가장 작은 프로젝트로 시작했고, 리팩토링된 구조는 해당 프로젝트에만 적용되어있다.
마치며
리팩토링을 고민하고 시작하게 된 계기는 이 강의를 듣고 나서였다.
리팩토링을 하면서 느낀 거지만, 이 강의는 헥사고날 아키텍처에서 테스트에 필요한 부분만 차용한 강의였다.
단순히 테스트를 위해 리팩토링 하는 것에서 그친게 아닌,
본인의 리팩토링 철학을 짧은 강의로 쉽게 녹여냈다는 걸 리팩토링을 하면서 느낄 수 있었다.
물론 강의 한편만 보고 시도해본 건 아니긴하다.
내가 마주한 문제를 해결하기 위한 한 방법이었을 뿐... 더 좋은 방법이 보이면 그 쪽을 알아보지 않을까?
'개발 > SPRING' 카테고리의 다른 글
SpringBoot에서 MultipartFile 전송하기 feat. FeignClient (1) | 2024.07.23 |
---|---|
Kotlin + SpringBoot 서버에 테스트 도입기. Kotest와 MockK (0) | 2024.07.19 |
OpenFeign 간단 사용법과 FeignException 핸들링하기 (0) | 2024.03.22 |
Google Document Translation API 사용하기 (with. Spring Boot) (1) | 2024.02.20 |
스프링부트에서 Multipart/form-data 요청의 MultipartFile 정보 로그 남기기 (1) | 2024.02.10 |
- Total
- Today
- Yesterday
- MySQL
- springboot
- OpenFeign
- cache
- EKS
- elasticsearch
- 스프링부트
- AOP
- 오블완
- openAI API
- OpenAI
- GIT
- lambda
- AWS EC2
- Kotlin
- 후쿠오카
- Spring
- ChatGPT
- java
- 람다
- Log
- terraform
- Elastic cloud
- S3
- AWS
- 티스토리챌린지
- JWT
- CloudFront
- serverless
- docker
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |