Pickle을 만들며 고른 기술들 (02) - 왜 Pickle은 모노레포 구조가 필요해졌을까
브라우저 익스텐션, 공개 웹, 클라이언트 앱이 함께 커지면서 왜 Pickle이 모노레포와 shared contracts를 택하게 되었는지 정리합니다.
표면이 하나일 때는 폴더 구조가 큰 문제가 아닙니다. 하지만 익스텐션, 공개 웹, 클라이언트 앱이 동시에 움직이기 시작하면, 구조는 취향이 아니라 비용 문제가 됩니다.
들어가며
Pickle에서 모노레포를 택한 이유를 한 문장으로 줄이면 이렇습니다. 처음부터 익스텐션과 웹 클라이언트가 같이 필요하다고 생각했고, 이 둘을 따로 놀게 두면 유지보수가 더 어려워질 것 같았습니다.
이 글에서는 왜 그 판단이 나왔는지, 그리고 왜 pnpm workspaces 기반의 모노레포가 Pickle에는 더 자연스러운 선택이었는지를 회고 형식으로 정리해 보려고 합니다.
왜 단일 앱 구조로는 설명하기 어려워졌는가
Pickle을 처음 떠올릴 때부터 분명했던 전제가 하나 있었습니다. 기존의 로컬 저장 기반 서비스들이 가진 불편을 줄이려면, 브라우저 안에서 바로 동작하는 확장프로그램이 필요하다는 점이었습니다. 그런데 여기서 바로 다른 문제가 따라옵니다. 저장은 익스텐션에서 시작하더라도, 저장한 내용을 다시 정리하고 보는 화면은 결국 웹에 있어야 한다는 점입니다.
즉 Pickle은 처음부터 표면이 둘 이상일 수밖에 없었습니다.
- 저장을 시작하는 곳은 익스텐션
- 저장한 내용을 다시 보는 곳은 웹 클라이언트
이 상태에서 두 코드를 완전히 따로 관리하면, 초반에는 빨라 보여도 금방 불편해질 것이라고 생각했습니다. 같은 기능을 다루는데 스타일과 규칙이 조금씩 달라지고, 공통으로 써야 하는 타입이나 유틸이 생길 때마다 중복이 쌓일 가능성이 높았기 때문입니다.
그래서 Pickle에서는 애초에 "각 앱을 따로 만든다"보다 "여러 표면을 하나의 프로젝트 안에서 관리한다"는 쪽을 먼저 떠올리게 됐습니다.
Pickle에서 모노레포가 맡은 역할
1. 표면을 역할별로 나누기
모노레포를 택했다고 해서 모든 것을 한 앱 안에 우겨 넣고 싶었던 것은 아닙니다. 오히려 반대에 가까웠습니다. 익스텐션과 웹 클라이언트는 하는 일이 다르기 때문에, 역할은 분리하되 같은 규칙 안에서 관리하고 싶었습니다.
지금 Pickle이 apps/web, apps/client, apps/extension처럼 나뉘어 있는 것도 그 결과입니다. 공개 웹은 서비스 소개와 접근 지점을 맡고, 클라이언트 앱은 실제로 저장한 내용을 정리하고 탐색하는 역할을 맡고, 익스텐션은 저장의 시작점이 됩니다.
이렇게 표면을 나눠두면 각 앱의 책임은 더 분명해집니다. 동시에 저장소는 하나이기 때문에, 프로젝트 전체가 어떤 방향으로 가고 있는지를 보기 더 쉬웠습니다.
2. packages/contracts를 단일 진실 공급원으로 두기
표면이 둘 이상이면 공통으로 바라봐야 하는 데이터도 생깁니다. Pickle에서는 노트, 사용자 상태, 워크스페이스 같은 개념이 여기에 해당했습니다.
이런 것들을 앱마다 따로 두면, 처음에는 빠르게 개발되는 것처럼 보여도 시간이 갈수록 어긋나기 쉽습니다. 한쪽에서 타입을 바꾸면 다른 쪽이 늦게 따라오고, 같은 이름을 쓰지만 실제 구조는 달라지는 일이 생길 수 있기 때문입니다.
그래서 Pickle에서는 공통으로 쓰는 타입과 스키마를 한곳에서 관리하는 쪽이 더 낫다고 봤습니다. 나중에 packages/contracts 같은 구조로 자리 잡은 것도 그 판단의 연장선에 있습니다. 모노레포는 이런 공통 계층을 억지스럽지 않게 두기 쉬운 구조였습니다.
3. UI와 아이콘을 공유하되 앱의 제약은 분리하기
Pickle에서 "통일된 스타일과 규칙"을 중요하게 본 이유도 여기에 있습니다. 익스텐션과 웹 클라이언트는 각각 다른 환경이지만, 같은 서비스라면 기본적인 시각 언어와 컴포넌트 규칙은 어느 정도 맞아 있어야 했습니다.
물론 모든 UI를 완전히 똑같이 가져갈 수는 없습니다. 익스텐션에는 익스텐션의 제약이 있고, 웹에는 웹의 제약이 있기 때문입니다. 그래서 목표는 완전한 공용화라기보다, 가능한 것은 공유하고 환경 차이 때문에 갈라져야 하는 부분은 분리하는 것이었습니다.
모노레포는 이 균형을 잡기에 꽤 편했습니다. 공통 UI, 아이콘, 타입을 같은 저장소 안에 두고 필요할 때 가져다 쓰되, 각 앱은 자기 제약에 맞게 구현을 이어갈 수 있었기 때문입니다.
모노레포가 만능은 아니었습니다
물론 모노레포를 택했다고 해서 모든 문제가 사라지는 것은 아닙니다. 오히려 그때부터는 다른 종류의 기준이 더 중요해집니다.
- 무엇을 공통으로 둘지 먼저 정해야 합니다.
- 앱 간 경계를 흐리지 않도록 관리해야 합니다.
- 패키지를 나누는 일보다, 왜 나누는지 설명할 수 있어야 합니다.
이 점에서 모노레포는 편한 선택이라기보다, 더 일관된 판단을 요구하는 선택에 가까웠습니다. 그래도 Pickle에서는 그 비용을 감수할 가치가 있다고 봤습니다. 애초에 여러 표면을 함께 가져가야 하는 프로젝트였기 때문입니다.
마치며
Pickle에서 모노레포는 유행을 따라간 선택이 아니라, 처음부터 여러 표면을 함께 다뤄야 한다는 전제에서 나온 구조였습니다. 익스텐션과 웹 클라이언트를 같은 스타일과 규칙 안에서 개발하고 싶었고, 공통 타입과 로직을 재사용할 가능성도 초반부터 높다고 봤기 때문입니다.
결국 이 선택은 "저장소를 예쁘게 정리한다"기보다, 여러 표면을 가진 서비스를 한 프로젝트로 유지하기 위한 방법에 더 가까웠습니다.