[외주] 트래블록 프로젝트 리팩토링 회고

앞서 세웠던 리팩토링 방향성에 대해

직접 리팩토링하면서 배운 점과 느꼈던 점에 대해 작성해보려고 한다.

 

리팩토링을 통해 해당 프로젝트에서 얻고자 했던 것들이 몇 가지 있었다.

 

  • 도메인 계층을 분리하여 비즈니스 로직을 보다 명확히 이동시키기
  • 데이터(DB)에 의존적인 코드가 아닌, 비즈니스 로직 중심의 코드로 변경하기
  • 의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기

 

 

왜 리팩토링이 필요한가?

이전에 작성했던 글에서는 리팩토링의 방향성만 정의하고,

왜 그 방향성을 선택했는지에 대한 설명이 부족했던 것 같다.

이번 글에서는 그 이유를 먼저 언급하고자 한다.

 

의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기 

리팩토링 방향성을 설정할 때, 가장 먼저 고민한 것은 왜 리팩토링이 필요한가?였다. 

프로젝트를 진행하면서 일관된 방식(레이어드 아키텍처)으로 코드를 구현하려고 노력했고,

스프링으로 개발하는 것은 처음이었기에, 팀원과 의논 후,

보편적으로 사용되는 Controller-Service-Repository 형태의 레이어드 아키텍처를 선택하게 되었다.

 

부끄럽게도 이번 외주 프로젝트를 진행하기 전에는 이것이 정답이라고 생각했다.

그렇다고 레이어드 아키텍처가 잘못되었다고 비판하는 것도 아니고 틀렸다는 건 절대 아니다.

 

후에 언급하겠지만,

토이프로젝트의 경우 굳이 복잡성을 늘리면서까지 개발을 할 필요가 있을까?라는 의문이 들었기 때문이다.

따라서 어떤 아키텍처가 정답이라는 건 존재하지 않는다.

 

레이어드 아키텍처를 사용하면서 점차 JpaRepository에 강하게 의존하므로서

 트랜잭션 스크립트 형태의 서비스 코드가 보여졌고,

그로 인해 서비스 코드의 대다수가 JPA에 의존적인 코드가 되었다.

 

트랜잭션 스크립트 

서비스 컴포넌트의 구현이 사실상 어떤 트랜잭션이 걸려있는 스크립트를 실행하는 것처럼 보일 때를 말한다.

→ 스마트 서비스 라고 부를 수 있다.

→ 절차지향에 가깝기에 변경에 취약하고 확장에 취약하며 업무가 병렬 처리되기 어렵다.

→ 서비스에 대한 올바른 정의가 필요

 

 

 

토이프로젝트를 진행함에 있어, JPA 외에 다른 기술을 사용하지 않을 예정이었지만

당시에 나는 이 부분이 크게 신경쓰였다.

이는 곧  리팩토링의 방향성으로 이어졌다.

 

도메인 계층을 분리하여 비즈니스 로직을 보다 명확히 이동시키기

더불어 서비스 계층에 많은 책임이 집중되면서 서비스 계층에 정의가 모호해졌고,

이에 대해 조치가 필요함을 느꼈다. 

그렇기에 서비스 계층을 애플리케이션 계층과 도메인 계층으로 나눔으로써 이를 해소하고자 했다.

 

추가로 테스트 코드 작성에 대한 막연한 두려움과 경험 부족으로 인해,

초기에는 테스트 코드를 작성하지 않고 단순히 API 개발에만 집중하게 되었다.

-> 단순, 포스트맨으로만 인수테스트를 진행할 뿐 더 이상의 테스트는 하지 않았다.. 지금와서 생각해보면 무섭다...

 

클래스를 작성하고 정의할 때, 행위에 대해 먼저 생각하고 그에 따라 상태를 결정하는 원칙을 나름대로 따랐다.

그러나 개발이 진행되면서 서비스 로직이 점점 비대해졌고, 그 결과 여러 레포지토리에 의존하는 서비스들이 생겨났다.

 

이런 구조는 테스트를 작성하는 데 어려움을 주었고,

특히 의존하고 있는 모든 레포지토리를 모킹하는 데 많은 복잡성이 따랐다.

 

이 상황에서 "어떻게 하면 테스트를 더 용이하게 할 수 있을까?"라는 고민을 하게 되었다.

프로젝트 코드를 분석하면서 서비스의 로직을 분리하여

조회 작업과 쓰기 작업에 사용되는 레포지토리를 분리하여 불필요한 의존성을 제거한다면

테스트 작성에 보다 용이할 수 있겠다라고 생각했다.

 

그렇기에 조회용 서비스와 그 외에 쓰기용 서비스를

각각 ReaderWriter로 나누어, 불필요한 의존성을 제거하는 방향으로 리팩토링 계획을 세웠다.

 

더불어, 각 클래스를 살펴보면서 책임을 보다 명확히 정의하고, 

서비스를 분리함과 동시에 필요시에는 추가로 서비스를 나눔으로써

단일 책임 원칙(SRP)을 최우선으로 지키는 방향으로 리팩토링을 진행하기로 했다.

 

이를 통해 서비스 로직이 간결해지고, 테스트 시에도 필요한 부분만 모킹하거나 대체할 수 있어,

테스트가 더 용이해질 것으로 기대했다.

 

결과적으로, 이 리팩토링의 주된 목적은 불필요한 의존성을 제거하고, 코드의 책임을 명확히 분리하여 유지보수성과 테스트 가능성을 향상시키는 것이었다.


 

리팩토링을 하면서 느낀 점

도메인 계층을 분리하여 비즈니스 로직을 보다 명확히 이동시키기

서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리하면서 얻을 수 있었던 장점을 나름대로 생각해봤다.

 

1. 책임의 명확한 분리 

 

 

애플리케이션 계층은 사용자 요청을 처리하고, 도메인 계층과 협력하여 필요한 작업을 수행하는 역할을 담당하도록 했다.

이 계층은 도메인 계층과 레포지토리가 협력할 수 있는 무대를 제공하며, 애플리케이션의 유스케이스(Use Case)를 구현하는 데 집중했다.

도메인 계층은 비즈니스 도메인을 표현하는 클래스들로, 비즈니스 문제를 해결하는 핵심 로직을 담도록  했다.

도메인 객체들끼리만 협력하도록 함으로써, 비즈니스 로직이 외부의 변화에 영향을 받지 않도록 하고, 도메인의 순수성을 유지할 수 있었다.

프로젝트가 복잡한 도메인을 다루지는 않았지만, 서비스 계층을 분리한 덕분에 코드가 더 단순하고 명확해졌음을 느낄 수 있었다.

 

2. 도메인 계층의 독립성 및 순수성 유지

도메인 계층을 다른 계층으로부터 분리함으로써,

도메인 객체들이 외부 라이브러리나 인프라스트럭처 계층에 의존하지 않도록 했다.

이로 인해 도메인 로직은 외부 변화에 흔들리지 않고, 오직 비즈니스 문제 해결에만 집중할 수 있었다.

또한, 도메인 계층의 코드가 다른 계층의 변화에 영향을 받지 않게 되어 유지보수가 더욱 용이해지고, 코드의 일관성을 유지할 수 있었다.

3. 테스트 용이성 향상

각 계층이 명확히 분리됨에 따라, 테스트도 훨씬 더 쉽게 수행할 수 있었다.

도메인 계층은 순수한 비즈니스 로직만을 담고 있기 때문에 외부 의존성이 없고, 단위 테스트가 훨씬 간편해졌다.

이를 통해, 테스트 코드 작성이 용이해졌다.

 

4. 유연한 확장성

애플리케이션 계층에서 새로운 유스케이스가 추가되더라도 도메인 계층을 수정할 필요가 없었고, 반대로 비즈니스 로직이 변화하더라도 애플리케이션 계층에 최소한의 영향만 미치게 되었다. 이러한 유연성 덕분에, 시스템을 더 쉽게 확장할 수 있게 되었다.

 

5. 유지보수성 향상

각 계층의 책임이 명확히 분리되어 있기 때문에, 특정 기능이나 로직을 찾고 수정하는 과정이 훨씬 간단해졌다. 이를 통해 시스템의 복잡성이 줄어들고, 유지보수가 용이해졌다.

 

이렇게 나름대로 느꼈던 것과 생각한 장점들을 나열해보았다.

 

결론적으로, 서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리함으로써,

책임 분리와 테스트 용이성, 도메인의 순수성 유지라는 여러 가지 장점을 얻을 수 있었다.

 

하지만! 오버엔지니어링이라는 생각이 들었다!

 

위에서 나열한 것처럼 장점만 있는 것은 아니었다. 

오히려 단점을 통해 스스로 깨달은 게 더 많았다...ㅠㅠ

 

이제는 서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리하면서 몇 가지 단점을 끄적여보려고 한다.

 

1. 도메인 객체 변환의 추가 작업

서비스 계층에서는 도메인 객체를 사용하기 위해,

레포지토리에서 반환되는 엔티티를 도메인 객체로 변환하는 작업을 신경 써야 했다.

이 과정에서 매핑 메서드를 만드는 변환 로직이 추가되면서 개발 속도가 느려질 수 있었다.

특히, 이 변환 작업이 반복되면서 실수로 특정 변수를 변환하지 않았던 적이 생겨서 제대로 된 응답을 내리지 못할 뻔 했다.

 

2. 구조의 복잡성 증가

애플리케이션 계층과 도메인 계층을 추가로 분리하면서, 전체 코드 구조가 복잡해졌다.

계층 간의 책임이 명확히 분리된다는 장점이 있지만, 그만큼 각 계층 간의 상호작용을 이해하고 관리하는 데 시간이 더 필요했다. 작은 프로젝트나 단순한 도메인을 다루는 경우에는 이 복잡성이 오히려 부담으로 작용할 수 있었다.

 

3. 오버엔지니어링의 우려

리팩토링을 진행하면서 가장 크게 느낀 점은, 현재 프로젝트가 복잡한 도메인을 다루지 않기 때문에, 이 정도로 계층을 분리할 필요가 있었을까 하는 고민이었다. 결과적으로, 이번 리팩토링이 오버엔지니어링에 가까웠다는 생각이 들었다. 복잡성이 낮은 도메인과 작은 규모의 애플리케이션에서는 이러한 계층 분리가 오히려 불필요하다는 점을 직접 경험하게 되었다.

배운 점

이번 리팩토링을 통해 몇 가지 배운 점이 있었다.

서비스 계층에 대한 이해도가 한층 높아졌고, 어떻게 하면 도메인에 집중할 수 있는 코드를 작성할 수 있을지에 대한 해답을 찾을 수 있었다.

또한, 복잡한 도메인을 다루는 경우에는 이런 식의 계층 분리가 유효한 해결책이 될 수 있다는 점도 배웠다.

결국, 이번 리팩토링은 오버엔지니어링이었다고 판단되지만,

이를 통해 복잡한 도메인 구조를 어떻게 다룰지에 대한 깊이 있는 이해와 소중한 경험을 얻었다는 점에서 큰 의미가 있었다. 이러한 경험을 바탕으로, 앞으로 더 나은 설계 결정을 내릴 수 있을 것이라 생각한다.

 

의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기

 

 

이번에는 의존성 역전 원칙(DIP)을 적용하여 얻은 장점과 단점을 정리해보면 다음과 같다.

대표적인 장점은 앞서 리팩토링 방향성에 대해 언급하면서 나왔던 내용이다.

장점

JPA에 대한 의존성 제거

의존성 역전 원칙(DIP)을 적용함으로써, 서비스(즉, 애플리케이션 계층)에서 JPA에 대한 직접적인 의존성을 제거할 수 있었다. 이를 통해 서비스 계층은 특정 기술 스택 (현재는 JPA)에 종속되지 않게 되었고, 코드의 유연성과 유지보수성이 향상되었다. 

 

 

 

단점

오버엔지니어링의 우려

현재 프로젝트에서는 JPA가 아닌 다른 기술 스택을 사용할 계획이 없었기 때문에, 이러한 의존성 제거가 실질적인 필요성에 부합하지 않았다. 결과적으로, 이는 오버엔지니어링에 가까운 접근이 되었고, 추가적인 복잡성을 불필요하게 초래했다. 구조적으로 복잡성이 증가함에 따라, 작은 규모의 프로젝트인 지금의 프로젝트에서는 오히려 관리가 더 어려워졌다고 생각된다.

 

배운 점 

DIP를 적용하여 얻은 장점은 분명 존재하지만, 프로젝트의 실제 요구사항과 규모를 고려하지 않은 채 적용하면 오버엔지니어링의 위험이 있다는 점을 깨닫게 되었다. 이러한 경험을 통해, 앞으로는 기술적 선택이 실제 필요와 적절히 맞아떨어지는지를 더 신중히 고려해야 한다는 교훈을 얻었다.

 

그럼에도 이번 리팩토링을 통해 DIP에 대한 이해도가 크게 높아졌고, 이러한 구조에 대한 깊이 있는 고민과 실제 적용 경험을 쌓을 수 있었다. 만약 이 리팩토링을 하지 않았다면, DIP를 실제로 프로젝트에 어떻게 적용해야 하는지에 대한 감각을 익히기 어려웠을 것이다. 이 경험은 향후 더 복잡한 도메인을 다루거나 유연성이 요구되는 프로젝트에서 큰 도움이 될 것이라 생각한다.

 

정리

아키텍처를 변경하는 코드 구조의 리팩토링에 있어서 신중한 결정이 필요하다는 점을 이번 경험을 통해 더욱 깊이 깨닫게 되었다. 흔히들 "아키텍처에는 상황에 따른 적절한 판단이 필요하다"고 말하지만, 실제로 리팩토링을 진행하면서 이 말의 진정한 의미를 몸소 체감했다.

 

비교적 간단한 프로젝트에서도 구조를 개선하는 데 많은 시간이 소요된다는 점을 실감했으며,

이는 아키텍처 변경이 단순히 기술적 선택에 그치는 것이 아니라, 프로젝트의 전체적인 방향성을 재검토하는 과정임을 깨닫게 해주었다.

 

물론, 내가 이를 직접 적용하면서 겪었던 시행착오도 없지 않았지만, 이러한 경험이 오히려 더 큰 배움으로 다가왔다.

리팩토링을 통해 코드 구조를 보는 관점이 넓어졌고, 어떤 아키텍처가 "좋다" 또는 "나쁘다"는 단순한 이분법적인 판단이 아닌, 상황에 맞는 적절한 선택이 중요하다는 점을 알게 되었다.

 

리팩토링의 방향성을 설정하는 과정에서도 많은 고민이 있었지만, 이를 실제로 적용하면서 매 순간 더 많은 고민이 필요했다. 그럼에도 불구하고 직접 리팩토링하고 코드를 개선해 나가는 과정에서 느낀 점이 훨씬 컸다고 생각한다.

 

의존성을 줄이고, 각 클래스의 단일 책임 원칙을 지키는 방향으로 리팩토링을 진행하면서, 단순히 글이나 책, 강의를 통해 얻을 수 없는 실질적인 경험과 인사이트를 얻을 수 있었다.

 

지금 이 블로그 글을 작성하는 시점이 리팩토링을 완료한 지 약 2주가 지난 시점이지만,

그 과정에서 얻은 인사이트와 성장에 대한 자신감은 여전히 크게 남아 있다. 비록 장단점에 대해 나열하는 글이 되긴 했지만, 이번 리팩토링을 통해 얻은 경험이 나에게는 소중한 자산이 되었다고 자부할 수 있다.

 

긴 글을 읽어주셔서 감사드리며, 만약 추가로 의견이 있으시다면 언제든지 댓글로 남겨주세요.

감사합니다!