icon
16장 : 실전 프로젝트

프로젝트 구조 설계


이전 장들에서는 타입스크립트를 활용한 프론트엔드와 백엔드 개발의 핵심 개념, 테스팅, 디버깅, 성능 최적화 등 다양한 기술을 학습했습니다. 이제 이러한 지식들을 통합하여 실제로 동작하는 애플리케이션을 구축하는 과정인 실전 프로젝트에 돌입할 시간입니다. 성공적인 프로젝트는 견고한 기반에서 시작되며, 그 기반의 핵심은 바로 프로젝트 구조 설계입니다.

프로젝트 구조 설계는 애플리케이션의 확장성, 유지보수성, 가독성에 직접적인 영향을 미칩니다. 잘 설계된 구조는 팀원 간의 협업을 용이하게 하고, 새로운 기능을 추가하거나 버그를 수정할 때 효율성을 높여줍니다. 이 절에서는 타입스크립트 기반의 풀 스택(Full-stack) 애플리케이션을 위한 효과적인 프로젝트 구조 설계 원칙과 실제 예시를 다룹니다.


프로젝트 구조 설계의 중요성

애플리케이션의 복잡성이 증가할수록 체계적인 구조의 필요성은 더욱 커집니다.

  • 유지보수성 향상: 논리적으로 잘 분리된 코드는 특정 기능을 수정하거나 버그를 찾을 때 관련 코드를 쉽게 찾을 수 있게 합니다.
  • 확장성 증대: 새로운 기능이나 모듈을 추가할 때 기존 코드에 미치는 영향을 최소화하고, 재사용 가능한 컴포넌트나 서비스 덕분에 빠르게 확장할 수 있습니다.
  • 가독성 및 이해도 증진: 일관된 구조는 개발자가 코드베이스를 빠르게 파악하고 이해하는 데 도움을 줍니다. 이는 특히 새로운 팀원이 프로젝트에 합류할 때 중요합니다.
  • 협업 효율 증대: 명확한 역할 분담과 구조는 여러 개발자가 동시에 작업할 때 코드 충돌을 줄이고 작업 흐름을 원활하게 합니다.
  • 테스트 용이성: 모듈화된 구조는 각 단위의 독립성을 높여 단위 테스트 작성을 용이하게 합니다.

일반적인 프로젝트 구조 설계 원칙

성공적인 프로젝트 구조를 위한 몇 가지 핵심 원칙을 소개합니다.

  1. 관심사의 분리 (Separation of Concerns): 각 코드가 단 하나의 책임만 가지도록 합니다. 예를 들어, UI 로직, 비즈니스 로직, 데이터 접근 로직은 서로 다른 파일이나 모듈에 분리되어야 합니다.

  2. 모듈성 (Modularity): 애플리케이션을 독립적이고 재사용 가능한 작은 단위(모듈)로 분할합니다. 각 모듈은 명확한 인터페이스를 가지며, 다른 모듈과의 의존성을 최소화합니다.

  3. 계층화 (Layering): 애플리케이션을 논리적인 계층으로 나누어 각 계층이 특정 역할만 수행하도록 합니다.

    • 프레젠테이션 계층 (Presentation Layer): 사용자 인터페이스(UI)를 담당합니다. (예: React 컴포넌트)
    • 애플리케이션/비즈니스 로직 계층 (Application/Business Logic Layer): 핵심 비즈니스 규칙과 애플리케이션의 기능을 정의합니다. (예: 서비스)
    • 데이터 접근 계층 (Data Access Layer): 데이터베이스나 외부 API와 상호작용합니다. (예: 리포지토리, ORM)
  4. 일관성 (Consistency): 폴더/파일 명명 규칙, 코드 스타일, 모듈 구성 방식 등 프로젝트 전반에 걸쳐 일관된 규칙을 적용합니다. ESLint, Prettier와 같은 도구를 활용하여 이를 강제할 수 있습니다.

  5. 재사용성 (Reusability): 공통적으로 사용될 수 있는 유틸리티 함수, 컴포넌트, 인터페이스 등을 별도의 모듈로 분리하여 재사용성을 높입니다.


풀 스택 타입스크립트 프로젝트 구조 예시

클라이언트(프론트엔드)와 서버(백엔드)가 결합된 풀 스택 타입스크립트 프로젝트의 일반적인 구조는 Monorepo (단일 저장소) 형태로 관리하는 경우가 많습니다. 이는 코드 공유 및 관리에 용이하기 때문입니다.

/my-fullstack-app
├── package.json               # Monorepo의 루트 package.json
├── tsconfig.json              # Monorepo의 루트 tsconfig.json (선택 사항)
├── /packages                  # 워크스페이스 (개별 프로젝트)
│   ├── /client                # 프론트엔드 애플리케이션 (예: React + Vite)
│   │   ├── public
│   │   ├── src
│   │   │   ├── /assets
│   │   │   ├── /components    # 재사용 가능한 UI 컴포넌트
│   │   │   ├── /pages         # 라우트별 페이지 컴포넌트
│   │   │   ├── /hooks         # 커스텀 훅
│   │   │   ├── /utils         # 공통 유틸리티 함수
│   │   │   ├── /services      # API 호출 로직 (백엔드와 연동)
│   │   │   ├── /store         # 상태 관리 (Redux, Zustand, Recoil 등)
│   │   │   ├── /types         # 클라이언트 전용 타입 정의 (선택 사항, shared 사용 가능)
│   │   │   ├── App.tsx
│   │   │   └── main.tsx
│   │   ├── tsconfig.json
│   │   ├── vite.config.ts
│   │   └── package.json
│   │
│   ├── /server                # 백엔드 애플리케이션 (예: NestJS)
│   │   ├── src
│   │   │   ├── /auth          # 인증/인가 모듈
│   │   │   ├── /user          # 사용자 관련 모듈 (컨트롤러, 서비스, 엔티티, DTO)
│   │   │   ├── /product       # 제품 관련 모듈
│   │   │   ├── /common        # 공통 미들웨어, 가드, 파이프 등
│   │   │   ├── /config        # 환경 설정 관련 파일
│   │   │   ├── app.module.ts
│   │   │   └── main.ts
│   │   ├── tsconfig.json
│   │   └── package.json
│   │
│   └── /shared                # 클라이언트와 서버 간 공유되는 코드 (타입, 유틸리티 등)
│       ├── src
│       │   ├── /interfaces    # 공통 인터페이스 (예: User, Product 타입)
│       │   ├── /constants     # 공통 상수
│       │   └── index.ts
│       ├── tsconfig.json
│       └── package.json

├── .gitignore
├── README.md
└── pnpm-workspace.yaml        # 또는 yarn.lock, .npmrc 등 Monorepo 도구 설정 파일

Monorepo (단일 저장소) 구조

pnpm, Yarn Workspaces, Nx, Lerna 같은 도구를 사용하여 Monorepo를 설정할 수 있습니다. Monorepo의 장점은 다음과 같습니다.

  • 코드 공유 용이: shared 패키지를 통해 프론트엔드와 백엔드 간에 타입 정의(DTO, 인터페이스) 등을 쉽게 공유할 수 있어 타입 불일치로 인한 오류를 줄입니다.
  • 의존성 관리: 루트 package.json에서 모든 패키지의 의존성을 중앙 집중식으로 관리하거나, 각 패키지에서 독립적으로 관리할 수 있습니다.
  • 단일 빌드/테스트 스크립트: 모든 프로젝트를 한 번에 빌드하거나 테스트할 수 있는 스크립트 설정이 용이합니다.

/packages/client (프론트엔드)

  • /src/components: 재사용 가능한 UI 컴포넌트들 (버튼, 입력 필드, 모달 등). 이들은 자체적인 상태를 가질 수 있지만, 비즈니스 로직에 종속되지 않도록 설계합니다.
  • /src/pages: 라우팅되는 각 페이지 컴포넌트들. 보통 components 폴더의 컴포넌트들을 조합하여 페이지를 구성하고, 데이터 페칭 로직이나 상태 관리 로직과 연결됩니다.
  • /src/hooks: 리액트의 useState, useEffect 등을 사용하여 특정 로직을 캡슐화한 커스텀 훅들. (예: useAuth, useFormValidation)
  • /src/utils: 특정 기능이나 도메인에 종속되지 않는 범용 유틸리티 함수들 (날짜 포맷팅, 문자열 처리 등).
  • /src/services: 백엔드 API와 통신하는 로직을 담당합니다. axios, fetch 등을 사용하여 HTTP 요청을 추상화하고, 공유된 shared/interfaces 타입을 사용합니다.
  • /src/store: 애플리케이션의 전역 상태를 관리하는 로직. (Redux, Zustand, Recoil, Jotai 등 선택)
  • /src/types: 클라이언트 전용 타입 정의 (예: 컴포넌트 props 타입, 특정 UI 상태 타입). 백엔드와 공유되는 타입은 shared에 둡니다.

/packages/server (백엔드)

NestJS를 기준으로 한 구조 예시입니다. NestJS는 자체적인 모듈화 규칙을 강력하게 권장합니다.

  • /src/auth: 사용자 인증(로그인, 회원가입, JWT) 및 인가(권한 확인) 관련 로직을 포함하는 모듈입니다.
    • auth.controller.ts
    • auth.service.ts
    • auth.module.ts
    • jwt.strategy.ts (passport.js와 연동 시)
    • guards (인증 가드)
    • dto (로그인 요청 DTO 등)
  • /src/user: 사용자 정보 관리(CRUD) 관련 모듈입니다.
    • user.controller.ts
    • user.service.ts
    • user.module.ts
    • entities/user.entity.ts (TypeORM 엔티티)
    • dto/create-user.dto.ts, update-user.dto.ts
  • /src/product: 제품 정보 관리 모듈. 위 user 모듈과 동일한 패턴으로 구성됩니다.
  • /src/common: 여러 모듈에서 공통적으로 사용되는 요소들 (글로벌 파이프, 가드, 인터셉터, 필터 등).
  • /src/config: 환경 변수 설정, 데이터베이스 연결 설정 등 애플리케이션 설정 관련 파일.
  • main.ts: 애플리케이션의 진입점. NestJS 애플리케이션을 부트스트랩합니다.
  • app.module.ts: 루트 모듈. 다른 모듈들을 임포트하고 전체 애플리케이션의 구조를 정의합니다.

/packages/shared (공유 코드)

이 폴더는 클라이언트와 서버 양쪽에서 사용될 코드와 타입 정의를 포함합니다.

  • /src/interfaces: User, Product, Order 등 핵심 비즈니스 객체에 대한 타입스크립트 인터페이스 또는 클래스 정의. API 요청/응답의 DTO(Data Transfer Object)와 일치시킵니다.
    • 백엔드에서는 TypeORM 엔티티나 NestJS DTO로 활용
    • 프론트엔드에서는 API 응답을 받을 때 타입 검증에 활용
  • /src/constants: 공통적으로 사용되는 상수 (예: API 경로, 에러 코드).
  • index.ts: shared 패키지의 공개 API를 내보내는 파일.

프로젝트 구조 설계 시 고려사항

  • 규모와 복잡성: 프로젝트의 규모와 예상되는 복잡성에 따라 구조를 결정합니다. 소규모 프로젝트는 더 간단한 구조를 가질 수 있습니다.
  • 팀 규모 및 협업 방식: 팀원 수, 팀 간의 협업 방식(프론트엔드/백엔드 분리 여부 등)을 고려하여 구조를 설계합니다.
  • 사용하는 프레임워크/라이브러리: NestJS와 같은 프레임워크는 특정 구조를 권장하므로, 이를 따르는 것이 프레임워크의 이점을 최대한 활용하는 방법입니다.
  • 테스트 전략: 테스트 코드를 어디에 배치하고 어떻게 실행할 것인지도 구조 설계에 영향을 미칩니다. (예: 테스트 파일은 해당 기능 파일 옆에 두는 *.test.ts 방식)
  • 배포 환경: Docker, Kubernetes와 같은 컨테이너 환경에서 배포할 경우, Monorepo 내의 각 애플리케이션을 별도의 이미지로 빌드하는 방식을 고려해야 합니다.

결론

프로젝트 구조 설계는 단순히 폴더를 만드는 행위를 넘어, 애플리케이션의 미래를 결정하는 중요한 첫 단계입니다. 타입스크립트 기반의 풀 스택 프로젝트에서는 모노레포를 활용하여 클라이언트와 서버 간의 코드 공유를 효율적으로 관리하고, 각 파트를 관심사의 분리 원칙에 따라 계층적으로 구성하는 것이 일반적입니다.

명확하고 일관된 구조는 코드의 가독성을 높이고, 유지보수와 확장성을 용이하게 하며, 팀원 간의 협업 효율을 극대화합니다. 다음 절부터는 이러한 설계 원칙을 바탕으로 실제 프로젝트의 각 구성 요소를 하나씩 구현해나가면서 학습한 개념들을 적용해 볼 것입니다.