본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 4. · 18 Views
프로젝트 구조와 모노레포 이해
대규모 프로젝트에서 여러 패키지를 효율적으로 관리하는 모노레포 구조를 알아봅니다. Bun과 Turborepo를 활용한 빌드 시스템부터 실무 코드베이스 탐색 방법까지 다룹니다.
목차
- 모노레포_구조_분석
- 핵심_패키지_opencode_plugin_sdk
- 지원_패키지_console_desktop_enterprise
- Bun과_Turborepo_빌드_시스템
- 개발_환경_설정
- 코드베이스_탐색_팁
1. 모노레포 구조 분석
김개발 씨가 새 회사에 입사한 첫 날, 코드 저장소를 클론받고 당황했습니다. 폴더가 수십 개나 있고, 각각 package.json을 가지고 있었기 때문입니다.
"이게 대체 뭐지? 프로젝트가 여러 개인 건가?" 선배 박시니어 씨가 다가와 웃으며 말했습니다.
"모노레포에 온 걸 환영해요."
모노레포는 여러 개의 프로젝트나 패키지를 하나의 저장소에서 관리하는 방식입니다. 마치 대형 쇼핑몰에 여러 브랜드 매장이 입점해 있는 것과 같습니다.
각 매장은 독립적으로 운영되지만, 건물 인프라는 공유합니다. 이 방식을 사용하면 코드 재사용이 쉬워지고, 프로젝트 간 의존성 관리가 한결 수월해집니다.
다음 코드를 살펴봅시다.
project-root/
├── packages/ # 핵심 패키지들이 모여있는 곳
│ ├── opencode/ # 메인 애플리케이션
│ ├── plugin/ # 플러그인 시스템
│ ├── sdk/ # 외부 연동용 SDK
│ ├── console/ # 관리자 콘솔
│ ├── desktop/ # 데스크톱 앱
│ └── enterprise/ # 기업용 기능
├── package.json # 루트 설정 파일
├── bun.lockb # 의존성 잠금 파일
└── turbo.json # Turborepo 빌드 설정
김개발 씨는 입사 첫 날의 당혹감을 아직도 기억합니다. 수십 개의 폴더, 수백 개의 파일이 눈앞에 펼쳐졌을 때 느꼈던 막막함이란.
과연 이 구조를 언제 다 파악할 수 있을까 걱정이 앞섰습니다. 박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갔습니다.
"걱정 마세요. 생각보다 단순해요.
일단 큰 그림부터 봅시다." 그렇다면 모노레포란 정확히 무엇일까요? 쉽게 비유하자면, 모노레포는 마치 대형 복합 쇼핑몰과 같습니다.
쇼핑몰 안에는 의류 매장, 식당, 영화관, 서점 등 다양한 가게가 있습니다. 각 가게는 독립적으로 운영되지만, 주차장이나 엘리베이터, 에어컨 같은 인프라는 함께 사용합니다.
모노레포도 마찬가지입니다. 각 패키지는 독립적인 기능을 가지지만, 빌드 시스템이나 린트 설정 같은 인프라는 공유합니다.
모노레포가 없던 시절에는 어땠을까요? 개발자들은 각 프로젝트를 별도의 저장소에서 관리했습니다.
이를 멀티레포라고 부릅니다. 문제는 프로젝트 간에 코드를 공유하기가 매우 번거로웠다는 점입니다.
공통 유틸리티 함수 하나를 수정하려면 별도 저장소에서 수정하고, 버전을 올리고, npm에 배포하고, 각 프로젝트에서 업데이트해야 했습니다. 작은 수정 하나에 이렇게 많은 단계가 필요했던 것입니다.
더 큰 문제는 버전 불일치였습니다. A 프로젝트는 공통 라이브러리 1.2.0을 쓰고, B 프로젝트는 1.3.0을 쓰는 상황이 빈번했습니다.
버그가 발생하면 어느 버전에서 생긴 건지 추적하기도 어려웠습니다. 바로 이런 문제를 해결하기 위해 모노레포가 등장했습니다.
모노레포를 사용하면 코드 공유가 즉각적입니다. 공통 함수를 수정하면 모든 패키지에서 바로 반영됩니다.
버전 관리의 복잡함이 사라지는 것입니다. 또한 하나의 PR로 여러 패키지의 변경 사항을 한꺼번에 리뷰할 수 있습니다.
무엇보다 전체 시스템의 일관성을 유지하기가 훨씬 쉬워집니다. 위의 폴더 구조를 살펴보겠습니다.
packages 폴더가 핵심입니다. 이 안에 모든 개별 패키지가 들어있습니다.
각 패키지는 자신만의 package.json을 가지고 있어서 독립적인 의존성 관리가 가능합니다. 루트에 있는 package.json은 전체 프로젝트의 공통 설정과 워크스페이스 정의를 담당합니다.
실제 현업에서는 어떻게 활용할까요? 구글, 페이스북, 마이크로소프트 같은 대형 IT 기업들이 모노레포를 적극 활용하고 있습니다.
예를 들어 구글은 수십억 줄의 코드를 단일 저장소에서 관리한다고 알려져 있습니다. 물론 우리가 그 정도 규모를 다룰 일은 드물겠지만, 중소 규모 프로젝트에서도 모노레포의 장점은 충분히 누릴 수 있습니다.
하지만 주의할 점도 있습니다. 모노레포가 만능은 아닙니다.
저장소 크기가 커지면 클론 시간이 오래 걸릴 수 있습니다. 또한 CI/CD 파이프라인 설정이 복잡해질 수 있습니다.
따라서 프로젝트 규모와 팀 상황을 고려해서 도입 여부를 결정해야 합니다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 폴더가 이렇게 나뉘어 있었군요. 이제 좀 감이 잡히네요!"
실전 팁
💡 - packages 폴더부터 살펴보세요. 각 패키지의 README를 읽으면 전체 구조 파악이 빨라집니다.
- 루트 package.json의 workspaces 설정을 확인하면 어떤 폴더가 패키지로 인식되는지 알 수 있습니다.
2. 핵심 패키지 opencode plugin sdk
김개발 씨가 packages 폴더를 열어보니 6개의 하위 폴더가 보였습니다. "이 중에서 어떤 것부터 봐야 할까요?" 박시니어 씨가 세 개의 폴더를 가리켰습니다.
"우선 opencode, plugin, sdk. 이 세 가지가 우리 서비스의 심장이에요."
핵심 패키지는 서비스의 주요 기능을 담당하는 패키지들입니다. opencode는 메인 애플리케이션, plugin은 확장 기능 시스템, sdk는 외부 연동을 위한 개발 도구입니다.
마치 자동차에서 엔진, 변속기, 연료 시스템이 핵심인 것처럼, 이 세 패키지가 전체 서비스의 근간을 이룹니다.
다음 코드를 살펴봅시다.
// packages/opencode/src/index.ts
// 메인 애플리케이션 진입점
export { App } from './app'
export { Router } from './router'
export { Store } from './store'
// packages/plugin/src/index.ts
// 플러그인 시스템 - 확장 기능 관리
export { PluginManager } from './manager'
export { createPlugin } from './factory'
export type { Plugin, PluginConfig } from './types'
// packages/sdk/src/index.ts
// 외부 개발자용 SDK
export { Client } from './client'
export { authenticate } from './auth'
export type { SDKOptions, Response } from './types'
김개발 씨는 박시니어 씨의 조언대로 세 개의 핵심 패키지부터 살펴보기로 했습니다. 마치 새로운 도시에 도착해서 주요 랜드마크부터 파악하는 것과 같은 이치였습니다.
첫 번째로 살펴볼 패키지는 opencode입니다. opencode는 마치 회사의 본사 건물과 같습니다.
모든 핵심 비즈니스 로직이 이곳에서 실행됩니다. 사용자가 서비스에 접속하면 가장 먼저 만나게 되는 코드가 바로 이 패키지에 있습니다.
라우팅, 상태 관리, UI 렌더링 등 애플리케이션의 뼈대가 되는 모든 것이 여기에 모여있습니다. 김개발 씨가 opencode 폴더를 열어보니 익숙한 구조가 보였습니다.
src 폴더 안에 components, pages, hooks 같은 하위 폴더들이 있었습니다. "아, 이건 제가 아는 구조네요!" 기존에 알던 프로젝트 구조와 크게 다르지 않아서 한결 마음이 놓였습니다.
두 번째 패키지는 plugin입니다. plugin 패키지는 마치 스마트폰의 앱스토어와 같습니다.
기본 기능만으로는 부족한 사용자들을 위해 추가 기능을 설치할 수 있게 해주는 시스템입니다. 플러그인 매니저가 플러그인의 등록, 활성화, 비활성화를 관리하고, 플러그인 팩토리가 새로운 플러그인 생성을 담당합니다.
왜 플러그인 시스템이 필요할까요? 모든 기능을 메인 애플리케이션에 넣으면 코드가 비대해집니다.
또한 모든 사용자에게 필요하지 않은 기능까지 포함되어 불필요한 리소스를 소비하게 됩니다. 플러그인 시스템을 통해 필요한 기능만 선택적으로 추가할 수 있게 하면 이런 문제를 해결할 수 있습니다.
세 번째 패키지는 sdk입니다. sdk는 마치 외교관과 같습니다.
우리 서비스와 외부 세계를 연결하는 다리 역할을 합니다. 다른 회사의 개발자가 우리 서비스와 연동하고 싶을 때, 직접 API를 호출하는 대신 SDK를 사용하면 훨씬 편리합니다.
인증 처리, 에러 핸들링, 타입 안정성 등을 SDK가 대신 처리해주기 때문입니다. 위의 코드를 살펴보면 각 패키지의 성격이 명확히 드러납니다.
opencode는 App, Router, Store 같은 애플리케이션 핵심 모듈을 내보냅니다. plugin은 PluginManager와 createPlugin 함수를 통해 플러그인 관리 기능을 제공합니다.
sdk는 Client와 authenticate 함수로 외부 연동에 필요한 도구를 제공합니다. 실무에서 이 구조가 왜 중요할까요?
새로운 기능을 추가할 때 어느 패키지에 코드를 작성해야 하는지 명확해집니다. 사용자 인터페이스 관련이면 opencode, 확장 기능이면 plugin, 외부 API 관련이면 sdk.
이렇게 역할이 분리되어 있으면 코드 리뷰도 수월해지고, 버그 추적도 빨라집니다. 주의할 점은 패키지 간 의존성 방향입니다.
일반적으로 sdk는 다른 내부 패키지에 의존하지 않아야 합니다. 외부 개발자가 사용하는 패키지이므로 독립적으로 동작해야 하기 때문입니다.
반면 opencode는 plugin과 sdk를 모두 활용할 수 있습니다. 이런 의존성 규칙을 지키지 않으면 순환 참조 문제가 발생할 수 있습니다.
김개발 씨는 메모장에 세 패키지의 역할을 정리했습니다. "opencode는 본사, plugin은 앱스토어, sdk는 외교관.
이렇게 기억하면 되겠네요!"
실전 팁
💡 - 새 기능 개발 전에 "이 기능은 어느 패키지에 속해야 하는가?"를 먼저 고민하세요.
- 각 패키지의 index.ts 파일을 보면 해당 패키지가 외부에 제공하는 기능을 한눈에 파악할 수 있습니다.
3. 지원 패키지 console desktop enterprise
박시니어 씨가 나머지 세 개의 폴더를 가리켰습니다. "console, desktop, enterprise.
이건 지원 패키지예요. 핵심 기능을 특정 환경이나 고객에게 맞춰서 제공하는 역할이죠." 김개발 씨는 궁금해졌습니다.
"핵심 패키지와 뭐가 다른 건가요?"
지원 패키지는 핵심 기능을 특정 환경이나 사용자 그룹에 맞게 확장하는 패키지들입니다. console은 관리자용 대시보드, desktop은 데스크톱 애플리케이션, enterprise는 기업 고객을 위한 추가 기능을 제공합니다.
마치 같은 음식이라도 배달용, 매장용, 케이터링용으로 포장이 달라지는 것과 비슷합니다.
다음 코드를 살펴봅시다.
// packages/console/src/index.ts
// 관리자 대시보드 - 시스템 모니터링 및 설정
export { Dashboard } from './dashboard'
export { Analytics } from './analytics'
export { UserManagement } from './users'
// packages/desktop/src/index.ts
// Electron 기반 데스크톱 앱
export { MainWindow } from './window'
export { TrayIcon } from './tray'
export { NativeMenu } from './menu'
// packages/enterprise/src/index.ts
// 기업 고객용 프리미엄 기능
export { SSO } from './sso' // Single Sign-On
export { AuditLog } from './audit' // 감사 로그
export { RBAC } from './rbac' // 역할 기반 접근 제어
핵심 패키지가 서비스의 심장이라면, 지원 패키지는 그 심장이 뛰는 힘을 다양한 곳에 전달하는 혈관과 같습니다. 김개발 씨는 박시니어 씨의 비유가 마음에 들었습니다.
첫 번째 지원 패키지는 console입니다. console은 마치 건물의 관제실과 같습니다.
일반 사용자는 들어갈 수 없지만, 관리자는 이곳에서 전체 시스템을 모니터링하고 제어합니다. 사용자 통계를 확인하고, 시스템 설정을 변경하고, 문제가 생기면 즉시 대응할 수 있는 대시보드가 이 패키지에 있습니다.
왜 console을 별도 패키지로 분리했을까요? 관리자 기능은 일반 사용자에게 노출되면 안 됩니다.
보안상 위험할 뿐만 아니라, 불필요한 코드가 사용자 번들에 포함되어 로딩 속도를 늦출 수 있습니다. 별도 패키지로 분리하면 관리자 페이지는 완전히 다른 진입점을 가지게 되어 이런 문제를 원천 차단할 수 있습니다.
두 번째 지원 패키지는 desktop입니다. desktop은 웹 애플리케이션을 데스크톱 환경에서 실행할 수 있게 해주는 패키지입니다.
보통 Electron이나 Tauri 같은 프레임워크를 사용합니다. 웹 기술로 만들어진 서비스를 Windows, Mac, Linux에서 네이티브 앱처럼 사용할 수 있게 해줍니다.
김개발 씨가 물었습니다. "웹으로도 쓸 수 있는데 왜 굳이 데스크톱 앱을 만드나요?" 박시니어 씨가 설명했습니다.
"오프라인 지원이 필요하거나, 시스템 트레이에 상주해야 하거나, 파일 시스템에 깊이 접근해야 할 때 데스크톱 앱이 필요해요. 슬랙이나 VS Code도 웹 기술 기반이지만 데스크톱 앱으로 제공하죠." 세 번째 지원 패키지는 enterprise입니다.
enterprise는 기업 고객만을 위한 프리미엄 기능을 담고 있습니다. 마치 호텔의 비즈니스 라운지와 같습니다.
일반 투숙객은 이용할 수 없지만, 비즈니스 클래스 고객에게는 특별한 서비스가 제공됩니다. 코드를 살펴보면 enterprise 패키지의 성격이 명확해집니다.
SSO(Single Sign-On)는 기업의 기존 인증 시스템과 연동하는 기능입니다. 직원들이 회사 계정으로 한 번만 로그인하면 여러 서비스를 이용할 수 있게 해줍니다.
AuditLog는 누가 언제 무엇을 했는지 기록하는 감사 로그입니다. 규제 준수가 중요한 금융이나 의료 분야에서 필수입니다.
RBAC(Role-Based Access Control)은 역할에 따라 접근 권한을 다르게 부여하는 시스템입니다. 실무에서 이런 구조가 주는 이점은 무엇일까요?
비즈니스 모델과 코드 구조가 일치합니다. 무료 사용자에게는 opencode만 제공하고, 유료 사용자에게는 enterprise까지 제공하는 식으로 패키지 단위로 기능을 구분할 수 있습니다.
라이선스 관리도 깔끔해지고, 어떤 고객이 어떤 기능을 사용하는지 추적하기도 쉬워집니다. 주의할 점은 패키지 간 경계를 명확히 유지하는 것입니다.
enterprise 기능이 opencode에 슬금슬금 들어가면 안 됩니다. 한 번 경계가 무너지면 나중에 분리하기가 매우 어려워집니다.
코드 리뷰 때 "이 기능이 이 패키지에 있는 게 맞나요?"라는 질문을 습관적으로 던지는 것이 좋습니다. 김개발 씨는 전체 그림이 그려지기 시작했습니다.
"핵심 패키지가 엔진이고, 지원 패키지가 다양한 차체인 거네요. 같은 엔진으로 세단도 만들고 SUV도 만드는 거죠!"
실전 팁
💡 - 지원 패키지는 핵심 패키지에 의존하지만, 그 반대는 안 됩니다. 의존성 방향을 항상 확인하세요.
- 새로운 기업 고객 요구사항이 들어오면 enterprise 패키지에 추가할지, 핵심 기능으로 올릴지 신중히 판단하세요.
4. Bun과 Turborepo 빌드 시스템
김개발 씨가 터미널을 열었습니다. "npm install 하면 되나요?" 박시니어 씨가 고개를 저었습니다.
"우리는 npm 대신 Bun을 쓰고, 빌드는 Turborepo가 담당해요. 훨씬 빠르거든요." 김개발 씨는 처음 듣는 이름에 눈을 동그랗게 떴습니다.
Bun은 JavaScript 런타임이자 패키지 매니저로, npm보다 훨씬 빠른 설치 속도를 자랑합니다. Turborepo는 모노레포 전용 빌드 시스템으로, 변경된 패키지만 선택적으로 빌드하여 시간을 절약합니다.
마치 택배 기사가 변경된 주소만 다시 배달하는 것처럼, 불필요한 작업을 건너뛰는 똑똑한 시스템입니다.
다음 코드를 살펴봅시다.
// turbo.json - Turborepo 설정 파일
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"], // 의존 패키지 먼저 빌드
"outputs": ["dist/**"] // 빌드 결과물 경로
},
"dev": {
"cache": false, // 개발 모드는 캐시 비활성화
"persistent": true // 지속적 실행 (watch 모드)
},
"test": {
"dependsOn": ["build"], // 빌드 후 테스트 실행
"outputs": ["coverage/**"]
}
}
}
김개발 씨는 이전 회사에서 npm install을 실행하고 커피 한 잔 마시고 오는 게 일상이었습니다. 의존성 설치에 5분, 빌드에 10분.
작은 수정 하나 테스트하려면 15분을 기다려야 했습니다. "여기도 그런가요?" 박시니어 씨가 웃으며 터미널에 명령어를 입력했습니다.
"한번 보세요." Bun이란 무엇일까요? Bun은 2022년에 등장한 새로운 JavaScript 런타임입니다.
Node.js의 대안으로 만들어졌으며, 속도에 특화되어 있습니다. Zig라는 저수준 언어로 작성되어 npm보다 패키지 설치가 최대 25배까지 빠릅니다.
마치 일반 도로 대신 고속도로를 이용하는 것과 같습니다. 김개발 씨가 bun install을 실행하자 정말로 눈 깜짝할 사이에 설치가 완료되었습니다.
"와, 진짜 빠르네요!" 하지만 빠른 설치만으로는 부족합니다. 모노레포에서는 빌드가 더 큰 문제입니다.
여기서 Turborepo가 등장합니다. Turborepo는 Vercel에서 만든 모노레포 빌드 시스템입니다.
핵심 아이디어는 증분 빌드입니다. 전체를 다시 빌드하는 대신, 변경된 부분만 빌드합니다.
또한 빌드 결과를 캐시해두어 같은 코드는 다시 빌드하지 않습니다. 비유하자면, 1000페이지 책을 인쇄할 때 오타 하나를 고쳤다고 전체를 다시 인쇄하지 않습니다.
해당 페이지만 다시 인쇄해서 교체하면 됩니다. Turborepo가 바로 이런 일을 해줍니다.
turbo.json 파일을 살펴보겠습니다. pipeline 섹션이 핵심입니다.
여기에 각 작업(task)의 실행 규칙을 정의합니다. build 작업의 **dependsOn: ["^build"]**는 "내가 의존하는 패키지들을 먼저 빌드하라"는 의미입니다.
캐럿(^) 기호가 의존 패키지를 가리킵니다. outputs는 빌드 결과물이 저장되는 경로입니다.
Turborepo는 이 경로를 보고 캐시 여부를 판단합니다. 입력 파일이 변경되지 않았고 출력 파일이 캐시에 있다면, 빌드를 건너뜁니다.
dev 작업에서 cache: false로 설정한 이유가 있습니다. 개발 중에는 항상 최신 코드를 실행해야 하므로 캐시를 사용하지 않습니다.
persistent: true는 watch 모드로 계속 실행되는 작업임을 의미합니다. 실제로 어떻게 동작할까요?
처음 turbo build를 실행하면 모든 패키지가 빌드됩니다. 시간이 좀 걸립니다.
하지만 두 번째 실행부터는 변경된 패키지만 빌드합니다. sdk 패키지만 수정했다면 sdk와 그에 의존하는 패키지만 다시 빌드됩니다.
나머지는 캐시에서 가져옵니다. 원격 캐시 기능도 있습니다.
Turborepo는 빌드 캐시를 클라우드에 저장할 수 있습니다. 팀원 A가 빌드한 결과를 팀원 B가 그대로 사용할 수 있다는 의미입니다.
CI/CD 파이프라인에서도 이전 빌드 결과를 재사용할 수 있어 배포 시간이 크게 단축됩니다. 주의할 점은 캐시 무효화입니다.
가끔 캐시가 오래되어 문제가 생길 수 있습니다. 이럴 때는 turbo build --force 명령으로 캐시를 무시하고 전체 빌드를 실행할 수 있습니다.
이상한 버그가 발생하면 먼저 캐시를 의심해보세요. 김개발 씨는 빌드가 30초 만에 끝나는 것을 보고 감탄했습니다.
"이전 회사였으면 10분은 걸렸을 텐데. 이거 정말 좋네요!"
실전 팁
💡 - turbo build --dry-run 명령으로 실제 빌드 없이 어떤 패키지가 빌드될지 미리 확인할 수 있습니다.
- .turbo 폴더가 캐시 저장소입니다. 문제가 생기면 이 폴더를 삭제하고 다시 빌드해보세요.
5. 개발 환경 설정
이제 실제로 개발 환경을 설정할 차례입니다. 김개발 씨는 README를 열어보았지만, 낯선 명령어들이 가득했습니다.
"bun install은 알겠는데, 그 다음은 뭘 해야 하죠?" 박시니어 씨가 단계별로 안내를 시작했습니다.
개발 환경 설정은 코드를 작성하기 전에 반드시 거쳐야 하는 과정입니다. Bun 설치부터 의존성 설치, 환경 변수 설정, 개발 서버 실행까지 순서대로 진행해야 합니다.
마치 요리를 시작하기 전에 재료를 준비하고 도구를 세팅하는 것과 같습니다. 이 과정을 제대로 하지 않으면 개발 중 예상치 못한 문제를 만나게 됩니다.
다음 코드를 살펴봅시다.
# 1. Bun 설치 (처음 한 번만)
curl -fsSL https://bun.sh/install | bash
# 2. 저장소 클론
git clone https://github.com/company/project.git
cd project
# 3. 의존성 설치 (모든 패키지의 의존성을 한 번에)
bun install
# 4. 환경 변수 설정
cp .env.example .env
# .env 파일을 열어 필요한 값 입력
# 5. 개발 서버 실행
bun dev # 모든 패키지 동시 실행
bun dev --filter opencode # 특정 패키지만 실행
김개발 씨는 새 노트북을 받았습니다. 깨끗한 상태에서 개발 환경을 처음부터 설정해야 합니다.
박시니어 씨가 옆에 앉아 하나씩 안내하기 시작했습니다. 가장 먼저 Bun을 설치합니다.
터미널을 열고 curl 명령어를 실행합니다. 설치 스크립트가 자동으로 Bun을 다운로드하고 설정합니다.
설치가 완료되면 터미널을 재시작하거나 source ~/.bashrc를 실행하여 환경 변수를 적용합니다. bun --version을 입력하여 설치가 잘 되었는지 확인합니다.
다음으로 저장소를 클론합니다. git clone 명령어로 원격 저장소의 코드를 로컬로 가져옵니다.
클론이 완료되면 해당 폴더로 이동합니다. 이제 로컬에서 코드를 수정하고 실행할 준비가 되었습니다.
세 번째 단계는 의존성 설치입니다. 여기서 모노레포의 장점이 빛납니다.
일반적인 멀티레포에서는 각 프로젝트 폴더에 들어가서 npm install을 각각 실행해야 합니다. 하지만 모노레포에서는 루트 폴더에서 bun install 한 번만 실행하면 됩니다.
Bun이 모든 패키지의 의존성을 알아서 설치해줍니다. 김개발 씨가 물었습니다.
"어떻게 한 번에 다 설치되는 거예요?" 박시니어 씨가 설명했습니다. "루트 package.json에 workspaces 설정이 있어요.
Bun은 이 설정을 보고 packages 폴더 안의 모든 패키지를 워크스페이스로 인식합니다. 그래서 한 번의 명령으로 전체 의존성을 설치할 수 있는 거죠." 네 번째 단계는 환경 변수 설정입니다.
대부분의 프로젝트는 API 키, 데이터베이스 연결 정보 같은 민감한 정보를 환경 변수로 관리합니다. 저장소에는 .env.example 파일이 있습니다.
이 파일을 복사해서 .env 파일을 만들고, 실제 값을 입력합니다. 환경 변수 파일은 절대로 git에 커밋하면 안 됩니다.
.gitignore에 .env가 포함되어 있는지 반드시 확인하세요. 민감한 정보가 저장소에 올라가면 보안 사고로 이어질 수 있습니다.
마지막으로 개발 서버를 실행합니다. bun dev를 실행하면 Turborepo가 모든 패키지의 개발 서버를 동시에 시작합니다.
각 패키지가 watch 모드로 실행되어 코드를 수정하면 자동으로 반영됩니다. 전체가 아닌 특정 패키지만 실행하고 싶다면 --filter 옵션을 사용합니다.
개발 중에 가장 흔한 문제는 무엇일까요? 첫째, 포트 충돌입니다.
여러 패키지가 동시에 실행되면 같은 포트를 사용하려다 에러가 발생할 수 있습니다. 각 패키지의 개발 서버 포트가 다르게 설정되어 있는지 확인하세요.
둘째, 환경 변수 누락입니다. .env 파일에 필요한 값이 없으면 런타임 에러가 발생합니다.
에러 메시지에 "undefined" 또는 "missing"이 보이면 환경 변수를 확인하세요. 셋째, 캐시 문제입니다.
이상한 버그가 발생하면 node_modules를 삭제하고 다시 설치해보세요. rm -rf node_modules && bun install 명령어로 깔끔하게 재설치할 수 있습니다.
김개발 씨는 bun dev를 실행하고 브라우저에서 localhost:3000을 열었습니다. 화면에 애플리케이션이 나타났습니다.
"드디어 됐다! 이제 코드를 수정해볼 수 있겠네요!"
실전 팁
💡 - bun run 대신 bun만 써도 됩니다. bun dev는 bun run dev의 축약형입니다.
- 문제가 생기면 bun install --force로 의존성을 강제로 재설치해보세요.
- 여러 터미널 탭을 열어 각 패키지를 개별적으로 실행하면 로그 확인이 편합니다.
6. 코드베이스 탐색 팁
개발 환경 설정을 마친 김개발 씨에게 첫 번째 과제가 주어졌습니다. "사용자 프로필 수정 기능에 버그가 있어요.
찾아서 고쳐주세요." 김개발 씨는 막막했습니다. 수천 개의 파일 중에서 어디부터 봐야 할까요?
대규모 코드베이스를 효율적으로 탐색하는 것은 중요한 개발 스킬입니다. IDE의 검색 기능, 파일 구조 이해, 진입점 파악, 의존성 추적 등 여러 기법을 활용하면 원하는 코드를 빠르게 찾을 수 있습니다.
마치 도서관에서 책을 찾을 때 분류 번호와 색인을 활용하는 것처럼, 코드베이스에도 효율적인 탐색 방법이 있습니다.
다음 코드를 살펴봅시다.
// 1. 전역 검색: 키워드로 빠르게 찾기
// VS Code: Ctrl+Shift+F (Windows) / Cmd+Shift+F (Mac)
// 검색어: "userProfile" 또는 "updateProfile"
// 2. 파일 검색: 파일명으로 찾기
// VS Code: Ctrl+P (Windows) / Cmd+P (Mac)
// 입력: "profile" → profile.ts, ProfileForm.tsx 등 표시
// 3. 정의로 이동: 함수나 변수의 정의 위치로 점프
// VS Code: F12 또는 Ctrl+Click
// 타입 정의: Ctrl+Shift+F10
// 4. 참조 찾기: 해당 코드를 사용하는 모든 곳 표시
// VS Code: Shift+F12
// 예: updateProfile 함수가 어디서 호출되는지 확인
// 5. 터미널에서 검색
grep -r "updateProfile" packages/ // 문자열 검색
find packages/ -name "*profile*" // 파일명 검색
김개발 씨는 넓은 도서관에 홀로 서 있는 기분이었습니다. 책이 수만 권 있는데, 특정 책을 찾으라니.
다행히 도서관에는 분류 체계와 검색 시스템이 있습니다. 코드베이스도 마찬가지입니다.
첫 번째 기법은 전역 검색입니다. 가장 직관적인 방법입니다.
버그가 "사용자 프로필 수정"과 관련되어 있다면, "profile"이나 "update"를 검색합니다. VS Code에서 Ctrl+Shift+F를 누르면 전체 프로젝트에서 검색할 수 있습니다.
검색 결과가 너무 많으면 파일 유형을 필터링하거나 검색어를 더 구체적으로 바꿉니다. 두 번째 기법은 진입점 찾기입니다.
웹 애플리케이션이라면 URL 라우팅을 먼저 봅니다. /profile 페이지가 어떤 컴포넌트를 렌더링하는지 확인합니다.
라우터 설정 파일(보통 router.ts나 routes.tsx)을 찾아보세요. 거기서부터 실제 페이지 컴포넌트로 연결됩니다.
김개발 씨가 라우터 파일을 열어보니 /profile/edit 경로가 ProfileEditPage 컴포넌트를 가리키고 있었습니다. 첫 번째 실마리를 찾은 것입니다.
세 번째 기법은 정의로 이동입니다. ProfileEditPage를 찾았다면, 그 안에서 사용하는 함수나 컴포넌트의 정의를 따라갑니다.
VS Code에서 함수 이름에 커서를 놓고 F12를 누르면 해당 함수가 정의된 파일로 바로 이동합니다. 마치 하이퍼링크를 클릭하듯이 코드를 따라갈 수 있습니다.
네 번째 기법은 참조 찾기입니다. 반대로, 특정 함수가 어디서 사용되는지 알고 싶을 때가 있습니다.
Shift+F12를 누르면 해당 코드를 참조하는 모든 위치가 표시됩니다. updateProfile 함수를 수정했을 때 어디에 영향을 미치는지 미리 파악할 수 있어 유용합니다.
다섯 번째 기법은 호출 스택 따라가기입니다. 버그가 발생하면 에러 메시지에 호출 스택이 표시됩니다.
가장 위에 있는 함수가 에러가 발생한 곳이고, 아래로 내려갈수록 그 함수를 호출한 상위 코드입니다. 호출 스택을 따라가면 버그의 근본 원인을 찾을 수 있습니다.
여섯 번째 기법은 Git 히스토리 활용입니다. "이 코드가 왜 이렇게 작성되었지?"라는 의문이 들 때 유용합니다.
git blame 명령어를 사용하면 각 줄을 누가, 언제, 어떤 커밋에서 작성했는지 알 수 있습니다. 해당 커밋 메시지를 읽으면 의도를 파악할 수 있습니다.
일곱 번째 기법은 README와 문서 먼저 읽기입니다. 각 패키지 폴더에는 보통 README.md 파일이 있습니다.
해당 패키지의 목적, 구조, 주요 API가 설명되어 있습니다. 코드를 직접 읽기 전에 문서를 먼저 훑어보면 전체적인 맥락을 빠르게 파악할 수 있습니다.
주의할 점은 검색에만 의존하지 않는 것입니다. 검색은 강력하지만, 코드의 구조를 이해하지 못하면 검색 결과를 제대로 해석하기 어렵습니다.
시간을 내서 폴더 구조를 천천히 살펴보세요. 각 폴더가 어떤 역할을 하는지, 파일들이 어떻게 조직되어 있는지 파악하면 나중에 검색 없이도 원하는 코드를 찾을 수 있게 됩니다.
김개발 씨는 이런 기법들을 활용해서 버그의 원인을 찾아냈습니다. ProfileForm 컴포넌트에서 상태 업데이트 로직에 오타가 있었던 것입니다.
수정하고 테스트하니 버그가 해결되었습니다. "처음엔 막막했는데, 방법을 알고 나니 찾을 수 있었네요!" 박시니어 씨가 덧붙였습니다.
"코드베이스 탐색 실력은 경험이 쌓이면서 늘어요. 지금은 시간이 걸리더라도, 6개월 후에는 눈 감고도 찾을 수 있을 거예요."
실전 팁
💡 - IDE의 단축키를 익히세요. 마우스보다 키보드가 훨씬 빠릅니다.
- 코드를 읽을 때 왜 이렇게 작성했는지 의도를 생각하며 읽으세요. 나중에 비슷한 패턴을 더 빨리 인식할 수 있습니다.
- 모르는 코드를 만나면 주석을 달아두세요. 나중에 다시 볼 때 도움이 됩니다.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
마이크로서비스 배포 완벽 가이드
Kubernetes를 활용한 마이크로서비스 배포의 핵심 개념부터 실전 운영까지, 초급 개발자도 쉽게 따라할 수 있는 완벽 가이드입니다. 실무에서 바로 적용 가능한 배포 전략과 노하우를 담았습니다.
Application Load Balancer 완벽 가이드
AWS의 Application Load Balancer를 처음 배우는 개발자를 위한 실전 가이드입니다. ALB 생성부터 ECS 연동, 헬스 체크, HTTPS 설정까지 실무에 필요한 모든 내용을 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.
고객 상담 AI 시스템 완벽 구축 가이드
AWS Bedrock Agent와 Knowledge Base를 활용하여 실시간 고객 상담 AI 시스템을 구축하는 방법을 단계별로 학습합니다. RAG 기반 지식 검색부터 Guardrails 안전 장치, 프론트엔드 연동까지 실무에 바로 적용 가능한 완전한 시스템을 만들어봅니다.
에러 처리와 폴백 완벽 가이드
AWS API 호출 시 발생하는 에러를 처리하고 폴백 전략을 구현하는 방법을 다룹니다. ThrottlingException부터 서킷 브레이커 패턴까지, 실전에서 바로 활용할 수 있는 안정적인 에러 처리 기법을 배웁니다.
AWS Bedrock 인용과 출처 표시 완벽 가이드
AWS Bedrock의 Citation 기능을 활용하여 AI 응답의 신뢰도를 높이는 방법을 배웁니다. 출처 추출부터 UI 표시, 검증까지 실무에서 바로 사용할 수 있는 완전한 가이드입니다.