
모노레포 구조에서 각 레포마다 prisma.schema 파일을 개별적으로 유지·관리하는 경우 여러 가지 어려움이 발생합니다.
prisma.schema를 수정해야 합니다.이러한 문제를 방지하기 위해, 모노레포 구조에서는 prisma.schema 파일을 단일하게 관리하는 방식이 적절하다고 판단했습니다.
이번 글에서는 이 문제를 해결하기 위한 접근 방법을 살펴봅니다.
MonoRepo는 여러 애플리케이션과 패키지를 하나의 저장소에서 관리하는 방식입니다.
동일한 DB를 사용하고, 유사한 기능을 제공하는 여러 서버를 운영하고 있는 현재 상황에서는 특히 적합한 구조라고 판단됩니다.
Prisma Client는 런타임에 동적으로 동작하는 ORM이 아니라, 빌드 시점에 생성되는 코드입니다.
prisma generate 명령을 실행하면
node_modules/.prisma/client 경로에 타입 세이프한 Prisma Client가 생성됩니다.
Sequelize와 TypeORM은 런타임에 메타데이터와 리플렉션을 기반으로 쿼리를 생성합니다.
반면 Prisma는 다음과 같은 방식으로 동작합니다.
prisma.schema를 빌드 타임에 해석이러한 차이로 인해 Prisma는 타입 안정성을 컴파일 단계에서 검증할 수 있으며,
문제가 발생할 경우 빌드 시점에 즉시 인지할 수 있습니다.
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는 하나의 Git 저장소(모노레포) 안에서 여러 개의 패키지를 하나의 프로젝트처럼 관리할 수 있도록 지원하는 기능입니다.
이를 통해 모노레포 내부의 디렉토리는 단순한 폴더가 아니라,
npm이 인식하는 정식 패키지가 되며, 패키지 간 의존 관계 역시 명확하게 표현할 수 있습니다.
Prisma 통합 관점에서 npm workspaces가 중요한 이유는,
Prisma Client를 더 이상 각 서버의 내부 구현물로 취급하지 않고,
모든 서버가 공통으로 사용하는 공유 라이브러리로 격상시킬 수 있기 때문입니다.
즉,
prisma.schema가 특정 서버(api, worker 등)에 귀속되지 않고,
모노레포 전체가 참조하는 단일 계약이 됩니다.
packages/prisma와 같은 공유 Prisma Client를 모든 서버가 의존하고 있다면,
해당 패키지 변경 시 관련 서버들은 재배포 대상이 됩니다.
이는 다음과 같은 이유에서 의도된 동작입니다.
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'
// 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를 명시적인 빌드 단계로 분리하는 것도 고려해야 합니다.
Prisma workspace 구조에서 migrations/는 사실상 DB 계약 변경 이력입니다.
이를 서버별로 관리하거나, 서버 배포 시점에 각자 migrate를 실행하면 다음과 같은 문제가 발생할 수 있습니다.
따라서 migrate는 반드시 단일 주체가 책임져야 합니다.
packages/prisma 변경은 이를 의존하는 서버들에 영향을 미칩니다.
서버별 독립 배포를 유지하더라도, 릴리즈 단위에 대한 합의는 필요합니다.
그렇지 않으면 서버별로 서로 다른 계약 버전이 공존하게 되어,
기존의 schema 불일치 문제로 되돌아가게 됩니다.
컬럼 삭제나 타입 변경과 같은 Breaking change는
서버별 배포 타이밍이 다를수록 위험해집니다.
다음과 같은 단계적 변경을 원칙으로 삼는 것이 좋습니다.
1단계: 컬럼/필드 추가 (호환)
2단계: 서버 코드 전환
3단계: 기존 컬럼/필드 제거
migrate는 DB에 직접 접근해야 하므로,
전용 파이프라인에는 다음 요소들이 필요합니다.
prisma generate는 DB를 변경하지 않고,
schema를 기반으로 Prisma Client를 생성하는 빌드 작업입니다.
packages/prisma의 책임이 원칙이 지켜지지 않으면,
서버별로 서로 다른 시점의 Client가 생성되어 schema 불일치 문제가 재발할 수 있습니다.
마이그레이션 실패 시 대응 방안 역시 사전에 정의되어야 합니다.
모노레포 환경에서 Prisma를 사용한다면,
schema 단일화는 선택이 아니라 구조적인 요구사항에 가깝습니다.