패키지 모노레포

사내 공통 로직의 일관성과 유지보수성을 높이기 위해 여러 레포에서 분산되어 있던 기능들을 하나의 패키지 모노레포로 통합해보았습니다.

8
조회

사내에서 공통으로 사용하는 로직들을 담고 있는 패키지 모노레포를 구축했습니다.

-- @teamsparta/utils --
math
- clamp.ts
- randomInt.ts
- ...
date
- isAfterOrEqual.ts
- getTimePastText.ts
- ...
-- @teamsparta/react --
hooks
- useBoolean.ts
- useInterval.ts
- ...
components
- Spacer.tsx
- InView.tsx
- ...


제가 일하는 회사는 약 20명의 개발 팀원들이 여러 개의 서비스를 운영하고 있습니다. 이 서비스들의 코드 베이스는 Github으로 관리하고 있고 구조는 멀티 레포입니다.

합류 이후 3개월이 지났을 때 저는 총 5개의 레포에 기여를 한 상태였습니다. 처음엔 꽤나 여기저기를 들쑤시고 다녔네요.

저희 회사는 적은 인원들이 몸과 시간을 갈아넣고, 중복을 허용하며, 어떻게든 되게 만들겠다는 의지 덕분에 가파른 성장을 경험할 수 있었습니다. 허나 가파른 성장 덕분에 코드 부채도 가파르게 늘어난 상태였습니다.

마침 저는 한창 @toss/slash와 같은 유틸리티성 로직들을 모아 놓은 패키지 모노레포에 관심이 많은 상태였습니다. 자연스레 이러한 저희 회사 상황에 패키지 모노레포를 도입해서 로직을 분리하면, 서비스 레포의 코드가 조금 더 깔끔해지지 않을까? 기존 서비스가 확장하거나 새로운 서비스가 생기더라도 공통 로직은 패키지 모노레포로부터 가져온다면 개발 부채의 증가를 어느 정도 막을 수 있지 않을까라는 생각을 하게 되었습니다.

달라질 수 있는 점

패키지 모노레포를 구축한다면 어떻게 달라질까요? 임의의 정수를 만들어내는 함수를 바탕으로 예를 들어보겠습니다.

A라는 레포에는 이 함수가 다음과 같이 구현되어 있는 반면,

// 생성
export function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
 
// 사용
import { randomInt } from '@/lib/math/randomInt';

B라는 레포에는 다음과 같이 구현되어 있습니다.

// 생성
export function random(minimum, maximum) {
  return Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
}
 
// 사용
import { random } from '@/utils/number/random';

다른 점이 보이시나요? 아주 사소할 수 있지만 이름이 다릅니다. 이름이 다른 걸 인지하고 다시 이해하는 건 사소하지만 생각보다 귀찮은 일입니다. 현재 random 정도는 짧아서 괜찮지만 조금이라도 긴 코드는 성가셔지기 마련이죠. 찾기도 어려워지구요!

그렇다면 이 randomInt를 사내 팀원들이 합의한 패키지로부터 가지고 올 수 있다면 어떨까요? 사용 방법은 패키지에 대한 문서를 확인하면 되고, 내부 구현은 패키지 속으로 숨겨집니다. 서비스 레포의 코드 베이스에서는 그저 이를 import해서 사용하면 됩니다.

import { randomInt } from '@teamsparta/utils';

팀원들과 함께 한 번만 잘 합의하고 논의해서 randomInt를 만들어놓으면 작은 분쟁과 중복들을 피할 수 있습니다. 이러한 작은 것들이 모이면 개발 생산성도 개선됩니다. 또한, 테스트 코드를 잘 작성해놓으면, 버그가 생겼을 때 randomInt에서는 아무런 문제가 없음을 빠르게 확인할 수도 있죠. 변경사항이 생기면 반영하는 것도 쉽습니다. randomInt가 있는 패키지만 변경해서 버전을 올린 후, 타 서비스에서는 패키지의 버전만 업데이트하면 되죠.

왜 직접 구현했을까..?

es-toolkit이나 usehooks-ts, react-if 등 잘 되어 있는 여러 패키지들을 가져다 사용하면 패키지 모노레포를 구축하지 않았어도 되는데 왜 직접 구축했을까요?

사실 개발자로서 패키지 모노레포을 직접 구축해보고 싶었다는 의지가 가장 컸습니다. 멋있는 작업을 해보고 싶었죠!

하지만 직접 구축하고 나니, 해보길 잘했다 싶은 점들이 많았습니다. 그 중 하나가 바로 의존성이 저희에게 있다는 점입니다. 잘 되어 있는 패키지라도 저희의 입맛에 맞게 사용하고 싶을 때가 있습니다. 이럴 땐 PR을 통해 기여를 하면 되겠지만 시간이 걸리고 그것이 통과조차 되지 않을 수도 있습니다. 이럴 때 직접 구축한 것이 빛을 발했습니다. 저희의 기준에 맞게 변경하고 추가하고 빠르게 전파할 수 있었죠.


모노레포, 패키징에 대한 개념만 있으면 구축하는 데에 큰 어려움은 없을 것 같습니다. 근데 전 꽤나 고전했던 기억이 있네요. 아무래도 패키지 모노레포에 대한 글이 생각보다 적기 때문인 것 같습니다. 혹여나 비슷한 작업을 하실 다른 분들을 위해 다음 편에서는 구체적으로 어떻게 코드를 작성했는지, 구체적으로 어떤 도구를 사용했는지에 대한 글을 작성해보도록 하겠습니다. 자동화에도 신경을 많이 썼습니다. 어떻게 하면 팀원들이 적극적으로 사용하고 기여할 수 있는 환경을 만들지 고민을 많이 했었어요. 이것에 대해서도 작성해보겠습니다.(사실 열린 마음과 함께 적극적으로 써준 팀원분들의 활약이 가장 컸었습니다)