MonoRepo 환경에서 Prisma Schema 통합 검토하기

seongha_h·2026년 1월 18일
post-thumbnail

개요

모노레포 구조에서 각 레포마다 prisma.schema 파일을 개별적으로 유지·관리하는 경우 여러 가지 어려움이 발생합니다.

  • DB 컬럼을 하나 추가할 때마다 모든 서버(repo)의 prisma.schema를 수정해야 합니다.
  • 반영되지 않더라도 해당 컬럼을 사용하지 않는다면 당장은 정상 동작할 수 있지만, 일부 서버만 최신 schema를 사용하는 상태가 됩니다. 이는 유지·보수 관점에서 바람직하지 않습니다.
  • 코드 변경이 없음에도 schema 변경만을 이유로 불필요한 재배포가 발생합니다.

이러한 문제를 방지하기 위해, 모노레포 구조에서는 prisma.schema 파일을 단일하게 관리하는 방식이 적절하다고 판단했습니다.
이번 글에서는 이 문제를 해결하기 위한 접근 방법을 살펴봅니다.


MonoRepo

MonoRepo는 여러 애플리케이션과 패키지를 하나의 저장소에서 관리하는 방식입니다.
동일한 DB를 사용하고, 유사한 기능을 제공하는 여러 서버를 운영하고 있는 현재 상황에서는 특히 적합한 구조라고 판단됩니다.


Prisma 동작 원리

Prisma는 런타임 ORM이 아니다

Prisma Client는 런타임에 동적으로 동작하는 ORM이 아니라, 빌드 시점에 생성되는 코드입니다.

prisma generate 명령을 실행하면
node_modules/.prisma/client 경로에 타입 세이프한 Prisma Client가 생성됩니다.


Sequelize, TypeORM과의 차이

Sequelize와 TypeORM은 런타임에 메타데이터와 리플렉션을 기반으로 쿼리를 생성합니다.

  • 엔티티 정의를 런타임에 해석
  • 실제 쿼리는 실행 시점에 구성
  • 타입 정의와 DB 스키마 불일치는 런타임 오류나 경고로 드러남

반면 Prisma는 다음과 같은 방식으로 동작합니다.

  • prisma.schema빌드 타임에 해석
  • 해당 스키마를 기반으로 Prisma Client 코드를 생성
  • 생성된 Client는 이미 고정된 타입과 쿼리 인터페이스를 가짐

이러한 차이로 인해 Prisma는 타입 안정성을 컴파일 단계에서 검증할 수 있으며,
문제가 발생할 경우 빌드 시점에 즉시 인지할 수 있습니다.


npm / node 빌드 관점에서 보는 문제

Node.js 생태계에서 패키지의 빌드 결과물은 기본적으로 node_modules를 기준으로 고정됩니다.
애플리케이션은 실행 시점에 node_modules에 존재하는 코드와 타입을 그대로 사용하며, 이 결과물은 런타임에 동적으로 변경되지 않습니다.

Prisma Client 역시 예외가 아닙니다.

@prisma/client는 단순한 라이브러리가 아니라,
prisma.schema를 입력값으로 하여 생성된 빌드 산출물입니다.

즉, 동일한 버전의 @prisma/client를 사용하더라도

  • 어떤 prisma.schema를 사용했는지
  • 언제 prisma generate가 실행되었는지

에 따라 완전히 다른 코드와 타입을 가진 Client가 생성될 수 있습니다.


정리

npm / node 빌드 관점에서 볼 때, Prisma schema를 서버별로 관리하는 구조는
하나의 DB를 여러 개의 빌드 산출물로 해석하는 구조이며, 이는 모노레포의 “공유” 철학과 충돌합니다.

따라서 모노레포 환경에서 Prisma를 사용할 경우,
prisma.schema는 애플리케이션 코드가 아니라 빌드 입력값이자 계약(Contract)으로 다뤄져야 하며,
단일한 위치에서 관리하는 것이 구조적으로 가장 자연스러운 선택입니다.


해결 방안

npm workspaces

npm workspaces는 하나의 Git 저장소(모노레포) 안에서 여러 개의 패키지를 하나의 프로젝트처럼 관리할 수 있도록 지원하는 기능입니다.

이를 통해 모노레포 내부의 디렉토리는 단순한 폴더가 아니라,
npm이 인식하는 정식 패키지가 되며, 패키지 간 의존 관계 역시 명확하게 표현할 수 있습니다.

Prisma 통합 관점에서 npm workspaces가 중요한 이유는,
Prisma Client를 더 이상 각 서버의 내부 구현물로 취급하지 않고,
모든 서버가 공통으로 사용하는 공유 라이브러리로 격상시킬 수 있기 때문입니다.

즉,

  • prisma.schema
  • Prisma Client 생성 결과물
  • DB 계약의 정의

가 특정 서버(api, worker 등)에 귀속되지 않고,
모노레포 전체가 참조하는 단일 계약이 됩니다.


주의점

packages/prisma와 같은 공유 Prisma Client를 모든 서버가 의존하고 있다면,
해당 패키지 변경 시 관련 서버들은 재배포 대상이 됩니다.

이는 다음과 같은 이유에서 의도된 동작입니다.

  • DB 계약이 변경되었는데 일부 서버만 이전 계약을 계속 사용하는 것이 더 큰 리스크
  • 공통 계약이 바뀌는 순간, 해당 계약을 사용하는 서비스가 함께 움직이는 것은 정상적인 비용

디렉토리 구조 예시

root/
├── package.json          # workspaces 정의
├── packages/
│   └── prisma/
│       ├── package.json
│       ├── schema.prisma
│       ├── migrations/
│       └── index.ts      # PrismaClient export
└── apps/
    ├── api/
    └── worker/
// root/package.json
{
  "workspaces": ["packages/*", "apps/*"]
}
// packages/prisma/package.json
{
  "name": "@myapp/prisma",
  "scripts": {
    "generate": "prisma generate",
    "migrate:dev": "prisma migrate dev",
    "migrate:deploy": "prisma migrate deploy"
  }
}
// packages/prisma/index.ts
import { PrismaClient } from '@prisma/client'

export * from '@prisma/client'
export const prisma = new PrismaClient()
// apps/api
import { prisma } from '@myapp/prisma'

generate 자동화 (postinstall)

// packages/prisma/package.json
{
  "scripts": {
    "postinstall": "prisma generate"
  }
}

npm install 시 자동으로 Prisma Client가 생성되어
CI/CD 환경에서 generate 누락을 방지할 수 있습니다.

실제 운영 환경에서는 서버 타입(API, Worker, Lambda)에 따라
PrismaClient 인스턴스 생성 전략(singleton, request scope 등)을
각 애플리케이션 레벨에서 조정하는 것이 안전합니다.
본 예시는 구조 설명을 위한 단순화된 형태입니다.

postinstall을 사용하는 경우,
CI/배포 환경과 로컬 환경 간 플랫폼 차이로 인해
Prisma 엔진 바이너리 다운로드 이슈가 발생할 수 있습니다.
운영 환경에서는 generate를 명시적인 빌드 단계로 분리하는 것도 고려해야 합니다.


도입 전 생각해야 할 것

1. DB migration 파이프라인 (단일 실행 주체) 추가

Prisma workspace 구조에서 migrations/는 사실상 DB 계약 변경 이력입니다.
이를 서버별로 관리하거나, 서버 배포 시점에 각자 migrate를 실행하면 다음과 같은 문제가 발생할 수 있습니다.

  • 마이그레이션 충돌
  • DDL 실행 시 락 경합
  • 롤링 배포·오토스케일 환경에서 단일 실행 보장 실패

따라서 migrate는 반드시 단일 주체가 책임져야 합니다.

  • 전용 CodePipeline/CodeBuild Job (권장)
  • 전용 서버(또는 메인 API) 단독 실행
  • 수동 승인 기반 실행 (운영 환경에서는 승인 필수)

2. 배포/릴리즈 단위 합의

packages/prisma 변경은 이를 의존하는 서버들에 영향을 미칩니다.
서버별 독립 배포를 유지하더라도, 릴리즈 단위에 대한 합의는 필요합니다.

  • 동일한 커밋 또는 태그 기준으로
    (1) DB migrate → (2) 서버 배포
    순서가 유지되어야 함

그렇지 않으면 서버별로 서로 다른 계약 버전이 공존하게 되어,
기존의 schema 불일치 문제로 되돌아가게 됩니다.


3. Breaking change 정책

컬럼 삭제나 타입 변경과 같은 Breaking change는
서버별 배포 타이밍이 다를수록 위험해집니다.

다음과 같은 단계적 변경을 원칙으로 삼는 것이 좋습니다.

1단계: 컬럼/필드 추가 (호환)
2단계: 서버 코드 전환
3단계: 기존 컬럼/필드 제거

4. migrate 실행 환경

migrate는 DB에 직접 접근해야 하므로,
전용 파이프라인에는 다음 요소들이 필요합니다.

  • DB 접근 권한 (VPC, 보안그룹, 네트워크 설정)
  • 시크릿 관리 (DB URL, 자격 증명)
  • 실행 환경 표준화 (누가 실행해도 동일한 결과)

5. prisma generate 책임 주체

prisma generate는 DB를 변경하지 않고,
schema를 기반으로 Prisma Client를 생성하는 빌드 작업입니다.

  • generate는 packages/prisma의 책임
  • 서버는 생성된 Client를 의존성으로 소비만 함

이 원칙이 지켜지지 않으면,
서버별로 서로 다른 시점의 Client가 생성되어 schema 불일치 문제가 재발할 수 있습니다.


6. 롤백 전략

마이그레이션 실패 시 대응 방안 역시 사전에 정의되어야 합니다.

  • Prisma는 기본적으로 down migration을 지원하지 않음
  • 롤백은 새로운 migration을 통해 되돌리는 방식
  • 운영 환경에서는 사전 백업 후 적용 권장

정리

  • Prisma schema는 설정 파일이 아니라 DB 계약이다
  • 계약이 분산되면 타입 안정성은 의미를 잃는다
  • npm workspaces는 Prisma Client를 공유 라이브러리로 만들기 위한 도구다
  • migrate는 반드시 단일 주체가 책임져야 한다
  • schema 변경 시 재배포는 비용이 아니라 안전장치

모노레포 환경에서 Prisma를 사용한다면,
schema 단일화는 선택이 아니라 구조적인 요구사항에 가깝습니다.

profile
https://github.com/Fixtar

0개의 댓글