왜 리팩터링을 시작했을까?
우아한테크코스 과정을 진행하면서 간단한 애프리케이션은 많이 사용해 봤지만, 모두 너무 작은 규모의 프로젝트였는데요
짧다면 짧고, 길다면 긴 기간동안 작성했던 프로젝트를 과거 코드부터 출발해서 변경해 나가는 과정을 직접 겪어보고 싶었습니다
이 프로젝트를 선택하게 된 이유는 외부 API 호출 기능이 많았기에,
테스트를 하기 위해서는 필수적으로 추상화나, 모킹이 들어가야 했습니다.
이때 잘 설계를 하게 된다면 추상화의 장점을 정확하게 볼 수 있다고 생각했는데요
기존 프로젝트에는 어떤 기능이 있을까?
1. GPT 를 통해서 꼬리질문을 만들어주는 기능
2. 가입을 하는 과정에서 이메일 인증을 하는 기능
3. 닉네임을 외부 api를 통해서 가져오는 기능
4. Security 로 로그인 관리
5. jpa 를 활용해서 db 관리
6. redis 를 활용해서 token 관리
이렇게 6가지 핵심 기능이 있었습니다
기존 프로젝트의 구조
총 4가지 패키지가 있습니다
1. Common 패키지
여기에 util 함수 1개가 존재하고, 총 6가지의 Configuration 이 담겨있습니다
1. Executor
스프링의 비동기 처리를 위한 설정입니다
2. Jasypt
Property 의 암호화를 담당하는 설정입니다
3. OpenAPI
OpenAPI의 설정이 담겨있습니다
4. QueryDSL
5. RedisRepository
6. RestTemplate
2. Domain
Domain이라고 되어있긴 하지만, 사실상 db entity 가 있고, JpaRepository, RedisRepository 가 존재하는 db 관련 패키지입니다
도메인 로직은 전혀 담겨있지 않은 순수한 데이터 계층입니다
3. Gpt
Gpt를 활용할 수 있도록 만들어진 gpt 관련 로직이 모여있습니다
여기서 domain을 알고 있어서, 직접 질문을 받아서, 꼬리질문을 만들어주는 역할을 하고 있습니다
4. Api
이 모듈은 사실 모든 것들을 다 하고 있는 모듈이었습니다
크게 4종류의 역할을 하는 클래스들이 모여있습니다
1. Controller
각종 controller 가 다 모여있습니다
2. Service
service에서 db entity 를 그대로 가져와서 getter 를 통해서 전부 다 데이터에 접근해 처리하는 과정이 모여있습니다
3. Dto
service 에서 dto 가 반환되어 controller 쪽으로 넘어갑니다.
4. Exception
발생할 수 있는 모든 종류의 예외는 커스텀 예외로 처리되어 있습니다
이때 Exception 은 httpCode에 대한 정보를 담고 있습니다
기존 프로젝트의 문제점
1. Service 가 너무 많은 것을 할 수 있다
사실상 모든 데이터가 다 service로 넘어가서, service에서 모든 데이터를 접근할 수 있기에, 한 곳에 수정이 이상한 영향을 줄 수 있습니다
2. 추상화가 되어있지 않다
모두 하위 레이어를 직접 호출하는 구조로 되어있습니다
가장 하위 레이어가 db 나 외부 api 호출로 이루어져 있기 때문에, 테스트코드를 작성하기 쉽지 않은 구조로 되어있었습니다
3. 어디에 무엇이 작성되어 있는지 명확하지 않다
기존 프로젝트에 common에 config라는 패키지가 있지만, 실제 프로젝트를 찾아가 보면 api/auth/config라는 패키지가 존재해 auth에 관한 config를 다루고 있었습니다
하나의 장소가 수정되면, 어디까지 영향을 받고 있는지를 직접 파악하기가 쉽자 않은 구조로 되어있었습니다
4. Exception 이 httpCode를 직접 들고 있는 구조
HttpCode를 직접 들고 있을 때의 문제점은 유지보수에서 문제가 생길 수 있다고 생각하는데요
추후에 개발자가, 이 예외가 터졌을 때, HttpCode 를 바꾸고 싶다는 생각을 한다는 가정을 해봅시다
ControllerAdvice를 찾아보고, 거기에 아무런 데이터를 찾을 수 없어서 직접 모든 예외를 다 찾아봐야 한다는 단점이 너무 명확합니다
5. Service에서 dto를 반환한다
서비스를 다른 서비스에서 참조했을 때, 전체가 하나의 물리적인 Transaction 임이 확실한 상황에서도 지연 로딩을 사용할 수 없고, 직접 repository에서 찾는 과정을 반복해야 합니다
개선 방향
- 지금의 패키지 구조, 의존성 관리 방식에서 멀티 모듈로 변경
- service에서 반환하는 dto를 제거하고, entity를 바로 반환하도록 변경 controller에서 dto로 변환
- 예외에 httpCode 가 적혀있던 부분 제거 ControllerAdvice에서 직접 예외 코드 설정
멀티모듈로 변경
멀티모듈로 변경했을 때, 아래와 같은 구조가 될 것으로 예상하고 있습니다
이런 구조가 되었을 때의 장점은 명확한데요
새로운 클래스를 추가하게 되었을 때, 이 클래스는 어디에 추가해야 할지가 정말 명확해집니다
또한 다른 모듈에 대한 참조를 쉽게 할 수 없기에, 정말 이 참조가 필요한 것인지에 대해서 고민해볼 기회를 갖게 됩니다
각각이 추상화가 되어있기에, 다른 레이어를 목 객체로 대신하여 테스트를 쉽게할 수 있을 것으로 기대하고 있습니다
Service에서 dto를 반환하지 않도록 변경
이 부분은 추후 고민을 하면서 도입하기로 정한 부분인데요
지금 당장 할 필요성까지는 없어 보이고, 기존의 구조를 유지해도 문제없다는 판단을 내리고, 우선순위를 뒤로 미루어 두었습니다
Exception에서 HttpCode 제거
예외에 관련된 부분은 모두 ControllerAdvice에서 처리할 예정입니다
물론 Advice 가 복잡해질 수는 있어도, 그 복잡함보다는 거기서 얻게 되는 장점이 더 큰 것 같아서 이 부분도 천천히 도입해 볼 예정입니다
계획 작성 후기
멀티 모듈로 작성했을 경우에, 캡슐화가 잘 지켜진다는 장점은 물론 있습니다
다른 레이어나, 다른 모듈 참조에 대해서 추상화가 잘 될 가능성이 높기에, 잘 사용하면 장점은 정말 크겠지만
잘못 사용하면 정말 커다란 실수가 될 수 있어 보이기도 합니다
간단하게 말씀드리면 복잡하니까요
설정해야 될 부분도 정말 많고, 발생하는 문제도 많기에, 하나하나 적어갈 예정입니다
더 자세한 구조에 대한 설명은 아래쪽 링크를 통해 확인해 보세요
https://github.com/effective-tech-interview/effective-tech-interview-be/issues/58
'프로젝트' 카테고리의 다른 글
카페인팀 서버 아키텍처를 설명해드리겠습니다 (7) | 2023.07.14 |
---|---|
프로젝트 git branch 전략 어떤 것이 있을까? (2) | 2023.06.28 |
쿠키로 Jwt RefreshToken 관리하기! (내 쿠키는 어디갔지?) (2) | 2023.05.15 |
ElasticCache 에 SpringDataRedis 에서 키 동기화 문제 (0) | 2023.04.30 |
이펙티브 기술면접 서비스 회고 (2) | 2023.04.02 |