개발자로 이리저리 회사에 다니면서 회사마다 개발자들이 어떤 것들을 집중하는지 많이 배웠던 것 같습니다.
최근에 들어간 회사에서는 개발자들이 비즈니스 로직에 집중하기 위해 불필요한 것을 자동화하는 시도들이 많이 있었고, 그러한 부분에서 많이 배운 것 같습니다.
그 중 특히 인상 깊었던 것은 불필요한 dto를 만들지 않기 위해 openApi generator를 사용해서 불필요한 코드 작성을 줄이는 모습이 있었습니다.
개인적으로 이전까지는 dto를 서버 개발자와 논의하는 것부터가 개발의 처음이라고 생각했지만, 중요한 로직을 만드는 것에만 집중하는 것이 진정으로 개발자가 나아가는 길이란 것을 배운 좋은 경험이었습니다.
그래서 사이드 프로젝트를 할 때도 최대한 불필요한 작업을 줄일 수 있는 기술을 선택해야겠다고 느꼈습니다.
사이드 프로젝트여서 공부하는 욕심도 있었지만, 서비스를 만드는 것에 집중하고 싶었습니다.
그래서 회사에서 어차피 ts 기반으로 개발하고 있었고, 하나의 언어로 서비스를 만들면 언어에 대한 컨텍스트 스위칭은 없이 서비스를 만들 수 있다고 생각했습니다
또한 i18n이나 다국가 대응 등을 관리하기 용이할 수 있고, 시간이나 ts에서 사용할 수 있는 유틸리티 등 공유할 수 있는 로직을 한번에 관리할 수 있을 것 같다는 기대를 했습니다.
그래서 React Native, NextJs, NestJs라는 ts 기반의 앱, 웹, 서버를 만들자는 생각 했습니다.
어차피 같은 언어로 서비스를 만들 수 있으니 모노레포 환경에서 만드는 게보다 효율적일 것으로 생각했습니다.
초기에 yarn workspace 기반에서 환경을 만들 생각 하였으나 pnp를 적용할 때 NestJs와 React Native에서 디펜던시를 잘 찾지 못하는 이슈가 있었고,
아래에 나올 tRPC를 활용하려면 app(React Native, NextJs)에서 app(NestJs)를 바라봐야 하는데 이 부분도 잘되지 않았습니다.
그래서 node-linker를 사용하면 일부 해소할 수 있었지만, 그럼에도 귀찮은 부분은 있었고 이러한 귀찮음을 감수하고 굳이 pnpm 대신 yarn을 쓸 필요가 없지 않을까 하는 고민이 있었습니다.
거기에 이어서 서버까지 함께 모노레포를 구축할 필요가 있을까 고민했습니다.
모노레포를 선택할 때 기존에 yarn workspace 방식으로는 생각보다 덜컹거림이 많아서 차라리 클라이언트 레이어 (React Native + NextJs)를 모노레포로 만들고 서버레이어 (NestJs)를 따로 나눌까도 생각했습니다. 이러한 부분에 대한 예제는 충분히 찾을 수 있었기 때문입니다.
어차피 운영에서는 서버와 클라이언트는 명확히 다르게 운영해야 하고, 서버가 바라보는 클라이언트 역시 앱과 웹, 그리고 더 장기적으로 운영하면 어드민페이지까지 관리해야 할 가능성이 있기 때문에 더욱 나눠주는 게 필요하겠다고 생각했습니다.
dto중복 정의는 기존 회사의 방식처럼 open API Generator를 사용한다면 어차피 한번 정의하면 두 레포에서 모두 쓸 수 있기 때문에 어쨌든 관심사가 명확히 다른 두 서비스를 나눌 이유가 있을까 생각했습니다.
그런데 이 선택이 필요에 의한 선택은 아니었고 무언가 안되기 때문에 다른 선택으로 도망가는 느낌을 받았습니다.
그래서 지금 당장 yarn workspace를 선택해야 할 명확한 이유가 없다면 yarn workspace를 포기하는 것이 목표를 달성하는 일이라고 생각해서 pnpm workspace를 선택했습니다.
그래서 “모노레포를 구축하자”라는 목표에 집중하겠다고 생각해 pnpm workspace를 선택했습니다.
물론 더 깊게 고민하면 좋은 해결책이 나올 수 있었지만 pnpm을 사용하지 말아야 할 이유도 크게 없었기 때문에
딱히 다른 이슈가 없고, 생각보다 낮은 러닝 커브로 monorepo환경을 구축할 수 있는 pnpm + turborepo를 사용하여 만들었습니다.
그렇게 모노레포를 만들게 됐고 api를 어떻게 서빙할지 고민했습니다.
평범한 RestFul한 api구조를 선택할지 고민하다가 예전부터 관심 있던 tRPC가 생각났습니다.
그리고 찾다 보니 ts rest라는 친구도 있었지만, 어차피 타입이 추론되는 가운데서 restful 함을 크게 지킬 이유가 없다고 생각했습니다.
바로 적용해보려했지만 tRPC를 활용한 예제들은 보통 NextJs에서 하나의 디렉토리에서 관리하는 것이 많았고, NestJs에서 tRPC를 활용하는 예시는 거의 없었습니다.
그런데 React Native에서도 tRPC기반의 NestJs의 API 서빙을 받고 싶은 목표가 있어서 NextJs하위에 넣는 선택은 할 수 없었고
https://www.tomray.dev/nestjs-nextjs-trpc 이러한 글을 기반으로 NestJs에서 TRP를 올리게 됐습니다.
초기 연결 자체는 잘 됐지만 API를 만들면서
라우팅을 어떻게 관리할지,
auth를 어떻게 관리할지,
NestJs에서 정의된 Date 타입이 response에서는 직렬화되어 string으로 내려오는 문제
등등 다양한 문제가 있었고, 이러한 내용은 후에 또 작성해 보겠습니다.
클라이언트 코드를 짤 때는 tRPC가 신의 한 수였습니다.
API의 dto를 따로 정의하지 않아도 자동으로 모든 타입이 추론되는 것은 물론 React Query 역시 tRPC에서 자동으로 만들어주기 때문에
API 관련된 불필요한 코드를 작성하지 않고 비즈니스 로직에만 집중할 수 있었습니다.
그리고 React Native에서 WebView를 열어줄 때 postMessage를 사용하는 부분에서도 WebViewBridge라는 https://gronxb.github.io/webview-bridge/getting-started.html 라이브러리를 사용하면서 message 관련된 타이핑을 줄일 수 있었습니다.
지금은 사이드 프로젝트이긴 하지만 스타트업의 첫 서비스에서도 새로 시작한다면 TS를 사용해서 서비스를 만드는 것은 좋다고 생각합니다!
특히나 요즘 tRPC뿐아니라 ts-rest elysia https://elysiajs.com/at-glance.html 같은 라이브러리 또한 e2e로 typeSafe한 환경에서 API를 서빙하는 것을 강점으로 내세우고 있기도 합니다.
그리고 서버와 클라이언트뿐 아니라 앱과 웹의 상호작용에서도 type safe 함을 지키며 불필요한 코드를 작성하지 않을 수 있다는 장점은 사람이 귀한 환경에서 엄청난 매리트가 될 수 있다고 생각합니다.
앞으로 tRPC를 쓰면서 겪은 이런저런 것들도 포스팅해 보겠습니다~