header_logo

COLDSURF BETA

#monorepo

#Refactor

Korean

Written by Paul

September 12, 2025

Photo by LATIKA SARKER on Unsplash
Photo by LATIKA SARKER on Unsplash
필자는 최근 굉장히 파괴적인 의사결정에 말미암아, 같은 기능을 한번 더 쌓게 되는 기이한 경험을 하게 된다. 단순히 리팩토링을 하는 것에서 더 나아가, 정말로 같은 기능이지만 다른 방식으로 처음부터 다시 쌓아보았다.
자세한 이야기는 회사측면의 이야기이므로, 어떻게 하다가 그러한 의사결정이 났는지는 다루지 않겠다. 이 글에서는 같은 기능을 두번 만들어 본 경험을 바탕으로, 어떻게 다르게 접근했는지와 함께 자신이 만든 레거시를 모노레포 패키지로 분할하며 정복하는 이야기에 대해 다루어 보려고 한다.

의존성

첫 번째로 던져보고 싶은 화두는 의존성이다.
대부분의 애플리케이션 서비스들은 entry point로 부터 시작되어, 여러 파일들에 의존성을 가지게 되어 서비스가 확장된다. 결국 서비스를 구성하는 각 기능들은 가장 단순한 포인트인 “import”문을 포함하여, 각 함수 및 변수에 대한 의존성 등등 다양한 의존성을 가지게 되어 서로 상호작용하며 동작하게 된다.
규모가 큰 서비스이거나, 여러가지 다양한 기능들이 포함된 서비스라면 이 의존성이 더 강한 결합을 가지고 있을 가능성이 높다.
이때에 설계를 자칫 잘못하게 되면 생길 수 있는 문제는 다음과 같다.
  • 기능이 워낙 많다보니, 해당 코드조각을 수정하였을때에 엮여있는 다른 기능에서 어떠한 사이드 이펙트가 나올지 예상이 쉽지 않은 경우
  • 해당 코드를 변경했지만, 코드상에서는 컴파일 혹은 빌드가 잘 되지만 실상은 기능상 버그가 있는 경우
  • 의존성이 너무 엮여 있어, 모듈화가 힘든 경우
이 외에도 예상하지 못하는 여러 경우가 생길 것이다.
코드를 변경하기 전에, 내가 바꾸는 이 코드를 어떤 곳에서 의존하고 있는지 알게 되면 리팩토링 혹은 기능 수정에 훨씬 자신감이 붙을 것이다.
따라서 필자는 다음과 같은 방식으로 바쁘게 개발되어 레거시를 남긴 부분 혹은 그 당시에 하고 싶었지만 못했던 코드 퀄리티를 챙기는 부분등을 가져갔다.

모노레포 패키지화

만약 내가 “src/lib/transaction”에 대한 부분을 리팩토링 한다고 하였을 때에, 해당 리팩토링이 성공적이게 마무리 되려면 적절한 선에서의 모듈화가 가능해야 한다. 그 말은 즉슨, 앱 내에서 “src/lib/transaction”을 똑 떼어내더라도 아름답게 떼어낼 수 있어야 한다는 말이 된다.
결국 아름답게 떼어내려면, 일단 떼내어 봐야 한다. 그 도구로써 필자는 모노레포 패키지를 선택했다.
앱 내에서 폴더구조를 바꾸는 것도 좋은 방법이지만, 그 안에서만 바꾸게 되면 앱에 종속적인 서비스 레이어를 품고 있기가 쉬운 구조라서, 순수 모듈화를 진행하기에는 한계가 있다.
따라서 아예 물리적으로 모노레포 패키지로 분리하여 앱 서비스 레이어의 종속성을 아예 끊어 보는 것이 가장 좋은 순수 모듈화를 진행하는 방법 중 하나라고 생각했다.
기존 “app/src/lib/transaction”에서 → “packages/transaction”으로 모노레포 패키지화를 진행했다.
이렇게 앱 서비스 의존성을 제거하게 되면 다음과 같은 가시성이 생긴다.
  • 앱 코드 내에서 에러가 나게 되어, 해당 부분을 참조하는 곳이 어디인지 알 수 있다
  • 모듈 코드 내에서 어떤 앱 서비스 레이어의 종속성을 가지고 있는지 알 수 있다
결국 앱↔모듈 간 엮여있는 공통 부분이 확 티가 나게 되는 것이다.
이 관점에서 보게 되면, 추상화가 가능해진다. 결국 모듈화를 위한 한 걸음을 나아가게 되는 것이다.

의존성 주입

다음과 같은 패턴들은 자주 마주치게 되는 리팩토링 상황일 것이다.
  • transaction 모듈에서는 singleton instance를 생성해야 한다
  • 해당 instance를 생성하기 위해서는, 앱 내의 .env 에 속해 있는 시크릿 값을 참조해야 한다
만약, 모노레포 패키지화를 진행하지 않았다면 해당 모듈에서 .env 의 시크릿 밸류를 그대로 import하여 참조할 수 있었을 것이다. 코드로 표현하게 되면 다음과 같다.
위와 같은 코드가 모노레포 패키지로 분리되게 되면 해당 process.env로 주입되던 값을 받아오는 패턴으로 변경되면 된다.
깔끔한 은닉화가 되고, 인터페이스가 조금 더 역할에 맞게 명확해진다. 사용처와 제공처가 구분이 되게 되니, 해당 역할에 대해서도 추후 알아보기도 쉬워진다.

가져와야 할 것과 아닌 것

모듈화 + 리팩토링을 진행할 때에 관심사를 분리하는 것은 아주 중요하다. 만약 그렇지 않고 모든 관심사를 포함하여 모듈화를 진행한다면, 이전과 동일한 방법론이 그대로 적용될 가능성이 높기 때문이다. 결국, 리팩토링이 아닌 물리적으로 위치만 분리하는 것에서 끝이 나게 될 가능성이 높다.
하지만, 대부분의 개발자들은 리팩토링을 진행하면서 얻으려고 하는 점은 단순히 공통 코드를 모아놓는 것이 아닌, “높은 코드의 퀄리티”“높은 유지보수성”일 것이다. 따라서 이 관점에서 어떤 부분은 과감히 대상에서 제외되어야 한다.
클라이언트 앱 내에서는 십중팔구 클라이언트 내부의 상태를 관리하려고 만드는 일명 store들이 있을 것이다. 이 store들은 높은 확률로, zustand나 jotai 혹은 redux와 같은 상태관리 라이브러리에 위임되는 경우들이 많을 것 같다. 이때에 이러한 앱내의 종속적인 상태관리 코드까지 가져오게 되면, 리팩토링의 범위가 굉장히 넓어진다.
만약 위와같이 상태관리까지 모듈화를 하게 된다면, 해당 모듈화된 패키지는 더 이상 하나의 관심사에 집중할 수 없게 된다. 따라서, 상태관리나 앱 서비스 종속적인 것들은 모듈화 리팩토링에서 최대한 배제하는 것이 좋다.
하지만, 다음과 같은 상황이 있을 수도 있다.
  • 여러 앱 서비스에서 동일한 상태관리 코드를 파편화 되어 관리하고 있다
  • 해당 액션 함수나, state 등은 모두 같은 역할을 하고 있으며 추상화가 가능하다
과연 이러한 상황에서 상태관리까지 같이 모듈화 및 추상화를 진행하는 것이 맞는가?라는 관점에서 보았을 때에는 “아니다”라고 이야기 하고 싶다.
첫 번째 이유로 “상태관리는 변경이 잦다”. 즉, 변경점이 많다는 것은 추상화가 언제든 바뀔 수 있다는 것을 뜻하며 그로인해 다른 서비스들도 해당 공통 코드로 인해 변경이 있어야 한다는 이야기가 된다.
두 번째 이유로 “상태관리 코드는 자유도가 높아야 한다”. 다수의 팀이나, 다수의 작업자가 본인의 작업을 잘 이끌어 나가려면 나름의 자유도가 존재해야 할 것이다. 상태관리까지 모듈화를 하게 되면, 작업자가 상태관리 방법에 종속적으로 끌려다니게 되므로 오히려 갇힌 사고방식에서 더 좋은 코드가 나올 수 없게 된다.
세 번째 이유로 “상태관리 라이브러리는 계속 진화중이다”. 상태관리 라이브러리는 잦은 version bump가 일어날 가능성이 높다. 그리고 해당 라이브러리의 breaking change 혹은 다른 라이브러리의 고려도 생각해야 하기 때문에 상태를 핸들링하는 코드는 섣부르게 모듈화 및 패키지화를 진행하는 것은 좋지 않다고 생각한다.
결국, 상태를 핸들링하는 코드는 앱 서비스 레이어에 종속적이다. 또한 기기(TV, 모바일, 웹)에 따라서 모두 같은 도메인에 대한 상태를 관리하지만 부분 부분 변경점이 다를 수 있다. 따라서 상태관리에 대한 부분은 앱 서비스내에 위임을 하는 것이 좋다고 생각한다.

빌드에 대한 생각

모노레포 패키지화를 진행할 때에는 다음과 같은 고민이 들기도 했다.
“꼭 빌드 절차를 거쳐야 할까?”
필자는 빌드 시스템은 체계화 하지 않았다, 대신 반복되는 설정인 lint 및 typescript, 그리고 test 환경은 따로 패키지로 분리한 설정을 그대로 위임받아 사용하는 방식을 택했다.
이유는 다음과 같았다.
  • 내가 리팩토링 하는 부분은, 현재 레포의 앱서비스 내에서만 쓰일 예정이므로, publish가 필요하지 않았다
  • 추후 물리적으로 분리된 더 많은 레포에서 모듈을 사용해야 한다면, publishable package로 승격하면 된다고 판단했다
  • 대부분의 FE Framework들은 naive하게 typescript 파일을 가져다가 써도, 번들러 설정만 잘 해준다면 큰 문제없이 잘 빌드한다
  • 코드를 변경 후 빌드까지 돌려야하는 거추장스러움은 피하고 싶었다
따라서, package.json 내의 main 필드를 src/index.ts 등으로 고정시키고 필요하면 사용하는 앱 서비스 내에서 babel alias 설정을 한다던지 하여 빌드 시스템을 스킵했다.

마무리

필자는 이러한 모노레포 패키지를 통한 일련의 분해와 조합방식을 통하여, 기능은 동일하지만 이전에 작성했던 코드보다 훨씬 안정성 있는 코드를 작성할 수 있었다고 생각한다. 관련하여 테스트 코드가 없었던 부분에 테스트 코드를 미약하게나마 붙일 수도 있었다. 테스트 코드를 잘 작성하려면 단위 함수별로 역할을 구분하여 잘 묶어두는 것이 중요하다. 이 과정도 해당 과정과 비슷한데, 기능별로 만들어진 함수 혹은 객체에 대한 역할 분리를 명확히 하여 앱 서비스 레이어와 섞이는 스파게티 코드를 방지하는 과정이었다고 생각한다.
 

© 2025 COLDSURF, Inc.

Privacy Policy

Terms of Service

Products