들어가기 전에
https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/is-prisma-an-orm
본 글은 Prisma
공식 문서의 해당 글 (Is Prisma an ORM?)을 번역한 문서입니다.
Prisma는 ORM인가요?
이 질문에 간단히 대답하자면, 맞습니다.
Prisma는 새로운 종류의 ORM이며, 전통적인 ORM들과는 근본적으로 다르고 이런 전통적인 ORM들이 겪는 다양한 문제들을 겪지 않습니다.
기존 ORM은 프로그래밍 언어의 모델 클래스에 테이블을 매핑하여 관계형 데이터베이스로 작업할 수 있는 객체 지향 방식을 제공합니다. 이 접근 방식은 객체-관계형 임피던스 불일치로 인해 발생하는 많은 문제를 야기합니다.
Prisma는 이와는 근본적으로 다른 방식으로 작동합니다. Prisma를 사용하면 당신의 데이터베이스 스키마와 당신의 프로그래밍 언어의 모델에 대한 단일 진실 공급원(Single source of truth)을 제공하는 선언적인 Prisma 스키마로 당신의 모델을 정의합니다.
그런 다음 애플리케이션 코드에서 Prisma 클라이언트를 사용하여 복잡한 모델 인스턴스를 관리하는 오버헤드 없이 Type-Safe한 방식으로 데이터베이스의 데이터를 읽고 쓸 수 있습니다. 따라서 데이터를 쿼리해오는 프로세스가 훨씬 더 자연스러워지고 예측 가능성도 높아집니다. Prisma 클라이언트는 항상 순수한 JavaScript 객체를 반환하기 때문입니다.
이 글에서는 ORM 패턴과 워크플로우, Prisma가 Data Mapper 패턴을 구현하는 방법, Prisma의 접근 방식의 이점에 대해 자세히 알아볼 것입니다.
ORM이 무엇인가요?
만약 당신이 ORM들에 이미 익숙하다면, 이 다음 섹션인 Prisma로 바로 이동해도 될 것 같습니다.
ORM의 패턴 - Active Record와 Data Mapper
ORM은 고수준의 데이터베이스 추상화를 제공합니다. 객체를 통해 프로그래밍 인터페이스를 노출하여 데이터베이스의 복잡성을 일부 숨기면서 데이터를 생성, 읽기, 삭제, 조작할 수 있습니다.
ORM의 개념은 모델을 데이터베이스의 테이블에 매핑되는 클래스로 정의하는 것입니다. 클래스와 그 인스턴스는 데이터베이스에서 데이터를 읽고 쓸 수 있는 프로그래밍 방식의 API를 제공합니다.
Active Record
와 Data Mapper
라는 두 가지 일반적인 ORM 패턴이 있습니다. 그리고 이 둘은 객체와 데이터베이스 간에 데이터를 전송하는 방식이 다릅니다. 두 패턴 모두 클래스를 기본 구성 요소로 정의해야 하지만, Data Mapper 패턴은 애플리케이션 코드의 in-memory 객체를 데이터베이스에서 분리하고 Data Mapper 계층을 사용하여 둘 간에 데이터를 전송한다는 점이 가장 큰 차이점입니다.
즉, Data Mapper를 사용하면 데이터베이스의 데이터를 나타내는 in-memory 객체는 데이터베이스가 존재한다는 사실조차 알지 못합니다.
Active Record
Active Record ORM은 모델 클래스를 데이터베이스의 테이블에 매핑하며, 두 표현의 구조가 밀접하게 관련된 경우(예: 모델 클래스의 각 필드에 데이터베이스 테이블의 일치하는 열이 있는 경우) 모델 클래스를 데이터베이스 테이블에 매핑합니다.
모델 클래스의 인스턴스는 데이터베이스 행을 래핑하고 데이터베이스의 지속적인 변경 사항을 처리하기 위한 데이터와 접근 로직을 모두 전달합니다. 또한 모델 클래스는 모델의 데이터와 관련된 비즈니스 로직을 전달할 수 있습니다.
모델 클래스에는 일반적으로 다음을 수행하는 메서드가 있습니다.
- SQL 쿼리로부터 모델의 인스턴스를 생성합니다.
- 나중에 테이블에 삽입할 새 인스턴스를 구성합니다.
- 일반적으로 사용되는 SQL 쿼리를 래핑하고 Active Record 객체를 반환합니다.
- 데이터베이스를 업데이트하고 Active Record의 데이터를 데이터베이스에 삽입합니다.
- 필드를 가져오고 설정합니다.
- 비즈니스 로직을 구현합니다.
Data Mapper
Data Mapper ORM은 Active Record와 달리, 데이터베이스의 표현으로부터 애플리케이션의 in-memory 데이터 표현을 분리(decouple)합니다. 이런 분리는 매핑의 책임을 두 가지 유형의 클래스로 분리하도록 요구함으로써 이루어집니다.
- 엔티티 클래스: 데이터베이스에 대한 지식이 없는 엔티티에 대한 애플리케이션의 in-memory 표현입니다.
- 매퍼 클래스: 매퍼 클래스는 다음 두 책임을 가집니다.
- 두 표현 (DB의 테이블과 애플리케이션의 모델 클래스)사이에서 데이터를 변환하기
- 데이터베이스에서 데이터를 가져오거나 변경 사항을 저장하는 데에 필요한 SQL 생성
Data Mapper ORM을 사용하면 코드에 구현된 문제 도메인과 데이터베이스 간에 더 큰 유연성을 확보할 수 있습니다. Data Mapper 패턴을 사용하면 데이터베이스가 구현되는 방식을 숨길 수 있습니다. (데이터베이스가 구현된 방식을 아는 것은 전체 데이터 매핑 계층 뒤에 있는 도메인을 생각하는 데에 이상적인 방법이 아님)
기존 Data Mapper ORM이 이 작업을 수행하는 이유 중 하나는 DBA와 백엔드 개발자와 같은 별도의 팀이 두 가지 업무를 담당하는 조직 구조 때문입니다.
실제로 모든 Data Mapper ORM이 이 패턴을 엄격하게 준수하는 것은 아닙니다. 예를 들어, Typescript 생태계는 Active Record와 Data Mapper를 모두 지원하는데, 여기에서 인기 있는 ORM인 TypeORM은 다음과 같은 Data Mapper 접근 방식을 취합니다.
- 엔티티 클래스는 데코레이터(
@Column
)를 사용하여 클래스 속성을 테이블 열에 매핑하고 데이터베이스를 인식합니다. - 매퍼 클래스 대신 리포지토리 클래스는 데이터베이스를 쿼리하는 데 사용되며 사용자 지정 쿼리를 포함할 수 있습니다. 리포지토리는 데코레이터를 사용하여 엔티티 속성과 데이터베이스 열 간의 매핑을 결정합니다.
다음과 같은 User
테이블이 데이터베이스에 존재한다고 가정해봅시다.
이에 해당하는 엔티티 클래스는 다음과 같이 생겼을 것입니다.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'first_name' })
firstName: string
@Column({ name: 'last_name' })
lastName: string
@Column({ unique: true })
email: string
}
스키마 마이그레이션 워크플로우
데이터베이스를 사용하는 애플리케이션 개발의 핵심은 새로운 기능을 수용하고 해결하려는 문제에 더 잘 맞도록 데이터베이스 스키마를 변경하는 것입니다. 이 섹션에서는 스키마 마이그레이션이 무엇이며 워크플로우에 어떤 영향을 미치는지에 대해 설명합니다.
ORM은 개발자와 데이터베이스의 사이에 위치하기 때문에 대부분의 ORM은 데이터베이스 스키마의 생성 및 수정을 지원하는 마이그레이션 도구를 제공합니다.
마이그레이션은 데이터베이스 스키마를 한 상태에서 다른 상태로 가져오는 일련의 단계입니다. 첫 번째 마이그레이션은 일반적으로 테이블과 인덱스를 생성합니다. 후속 마이그레이션에서는 열을 추가 또는 제거하거나, 새 인덱스를 도입하거나, 새 테이블을 만들 수 있습니다. 마이그레이션 도구에 따라 마이그레이션은 SQL 문 또는 프로그래밍 코드의 형태로 이루어질 수 있으며, 이 코드는 SQL 문으로 변환됩니다(ActiveRecord 및 SQLAlchemy와 같이).
데이터베이스에는 일반적으로 데이터가 포함되어 있으므로 마이그레이션을 통해 스키마 변경 사항을 더 작은 단위로 세분화하면 의도치 않은 데이터 손실을 방지하는 데 도움이 됩니다.
프로젝트를 처음부터 시작한다고 가정하면 전체 워크플로는 다음과 같습니다. 데이터베이스 스키마에 User
테이블을 생성하는 마이그레이션을 만들고 위의 예제에서와 같이 User
엔티티 클래스를 정의합니다.
그런 다음 프로젝트가 진행되면서 User
테이블에 새 salutation
열을 추가하기로 결정하면 테이블을 변경하고 salutation
열을 추가하는 또 다른 마이그레이션을 생성합니다.
TypeORM의 마이그레이션에서는 다음과 같이 생긴 코드가 작동합니다.
import { MigrationInterface, QueryRunner } from 'typeorm'
export class UserRefactoring1604448000 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "User" ADD COLUMN "salutation" TEXT`)
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "salutation"`)
}
}
마이그레이션이 수행되고 데이터베이스 스키마가 변경되면 새 salutation
열을 고려하도록 엔티티 및 매퍼 클래스도 업데이트해야 합니다.
TypeORM에서는 User
엔티티 클래스에 다음과 같이 salutation
속성을 추가하게 됩니다.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'first_name' })
firstName: string
@Column({ name: 'last_name' })
lastName: string
@Column({ unique: true })
email: string
@Column()
salutation: string
}
ORM에서 이러한 변경 사항을 동기화하는 것은 변경 사항이 수동으로 적용되고, 프로그래밍으로 쉽게 검증할 수 없기 때문에 꽤 어려운 일일 수 있습니다. 기존에 존재하던 열의 이름을 변경하는 것은 훨씬 더 번거로울 수도 있으며 해당 열에 대한 참조를 검색하고 교체해야 합니다.
참고: Django의 makemigrations CLI는 모델의 변경 사항을 검사하여 마이그레이션을 생성하므로 Prisma와 유사하게 동기화 문제를 해결합니다.
요약하자면, 스키마를 발전시키는 것은 애플리케이션 구축의 핵심 부분입니다. ORM의 경우 스키마를 업데이트하는 워크플로에는 마이그레이션 도구를 사용하여 마이그레이션을 생성한 다음 구현에 따라 해당 엔티티 및 매퍼 클래스를 업데이트하는 작업이 포함됩니다. 보시다시피 Prisma는 이에 대해 다른 접근 방식을 취합니다.
이제 마이그레이션이 무엇이며 개발 워크플로우에 어떻게 적용되는지 살펴보았으므로 ORM의 장점과 단점에 대해 자세히 알아보겠습니다.
ORM의 장점
개발자가 ORM을 사용하는 데에는 여러 가지 이유가 있습니다.
- ORM은 도메인 모델 구현을 용이하게 합니다. 도메인 모델은 비즈니스 로직의 동작과 데이터를 통합하는 객체 모델입니다. 즉, 데이터베이스 구조나 SQL 작성이 아닌 실제 비즈니스 로직에 집중할 수 있습니다.
- ORM은 코드의 양을 줄이는 데 도움이 됩니다. 일반적인 CRUD 작업을 위해 반복적인 SQL 문을 작성하지 않아도 되고, 사용자 입력을 이스케이프 처리하여 SQL 인젝션과 같은 보안 취약점을 방지할 수 있습니다.
- ORM을 사용하면 SQL을 거의 또는 전혀 작성할 필요가 없습니다(복잡성에 따라 여전히 이상한 로우 쿼리를 작성해야 할 수도 있음). 이는 SQL에 익숙하지 않지만 데이터베이스로 작업하고자 하는 개발자에게 유용합니다.
- 많은 ORM은 데이터베이스별 세부 사항을 추상화합니다. 이론적으로는 ORM을 사용하면 한 데이터베이스에서 다른 데이터베이스로 쉽게 변경할 수 있다는 뜻입니다. 실제로 애플리케이션에서 사용하는 데이터베이스를 변경하는 경우는 거의 없다는 점에 유의해야 합니다.
생산성 향상을 목표로 하는 모든 추상화가 그러하듯, ORM을 사용하는 데에는 단점도 있습니다.
ORM의 단점
ORM의 단점은 사용을 시작할 때 항상 명확하게 드러나는 것은 아닙니다. 이 섹션에서는 일반적으로 인정되는 몇 가지 단점을 다룹니다.
- ORM을 사용하면 데이터베이스 테이블의 객체 그래프 표현이 형성되어 객체 관계형 임피던스 불일치가 발생할 수 있습니다. 이는 해결하려는 문제가 관계형 데이터베이스에 쉽게 매핑되지 않는 복잡한 객체 그래프를 형성할 때 발생합니다. 관계형 데이터베이스와 인메모리(객체 포함)의 서로 다른 두 가지 데이터 표현 간에 동기화하는 것은 매우 어렵습니다. 이는 관계형 데이터베이스 레코드에 비해 객체가 서로 연관될 수 있는 방식이 더 유연하고 다양하기 때문입니다.
- ORM이 이 문제와 관련된 복잡성을 처리하지만 동기화 문제는 사라지지 않습니다. 데이터베이스 스키마나 데이터 모델을 변경하려면 변경 사항을 다른 쪽에 다시 매핑해야 합니다. 이러한 부담은 주로 개발자에게 있습니다. 프로젝트를 진행하는 팀에서 데이터베이스 스키마를 변경하려면 조율이 필요합니다.
- ORM은 캡슐화되는 복잡성으로 인해 API 표면이 큰 경향이 있습니다. SQL을 작성할 필요가 없다는 것은 ORM 사용법을 배우는 데 많은 시간을 할애해야 한다는 단점이 있습니다. 이는 대부분의 추상화에 적용되지만 데이터베이스가 어떻게 작동하는지 이해하지 못하면 슬로우 쿼리를 개선하기에 어려울 수 있습니다.
- 일부 복잡한 쿼리는 SQL이 제공하는 유연성 때문에 ORM에서 지원되지 않습니다. 이 문제는 원시 SQL 쿼리 기능으로 완화할 수 있는데, 이 기능은 ORM에 SQL 문 문자열을 전달하면 쿼리가 대신 실행됩니다.
이제 ORM의 비용과 이점을 살펴보았으니, Prisma가 무엇이고 어떻게 적합한지 더 잘 이해할 수 있을 것입니다.
Prisma
Prisma는 애플리케이션 개발자가 데이터베이스를 가지고 일하는 것을 쉽게 만들어주는 도구와 기능들을 제공하는 차세대 ORM입니다.
Prisma는 다음과 같은 도구를 제공합니다.
- Prisma Client: 당신의 애플리케이션에 사용할 수 있는 자동생성 및 타입안전한 데이터베이스 클라이언트 툴
- Prisma Migrate: 선언적인 데이터 모델링과 마이그레이션 툴
- Prisma Studio: 데이터베이스의 데이터를 관리하고 표시해주는 모던한 GUI
참고: Prisma Client가 가장 눈에 띄는 도구이므로, Prisma Client를 간단히 Prisma라고 부르는 경우가 많습니다.
이 세 가지 도구는 데이터베이스 스키마, 애플리케이션의 객체 스키마 및 둘 사이의 매핑에 대한 단일 진실 공급원으로써 Prisma 스키마를 사용합니다. 스키마는 사용자가 정의하며 Prisma의 기본 구성 파일입니다.
Prisma는 타입 안전, 풍부한 자동 완성, 연관관계 가져오기를 위한 자연스러운 API와 같은 기능을 통해 구축 중인 소프트웨어의 생산성과 자신감을 높여줍니다.
다음 섹션에서 Prisma가 Data Mapper ORM 패턴을 구현하는 방법에 대해 알아봅시다.
Prisma가 Data Mapper 패턴을 구현한 방법
앞서 언급했던 것처럼, Data Mapper 패턴은 데이터베이스와 애플리케이션을 서로 다른 팀이 소유하고 있는 조직에 잘 어울립니다.
관리형 데이터베이스 서비스와 DevOps 관행을 갖춘 최신 클라우드 환경이 부상함에 따라, 더 많은 팀이 데이터베이스와 운영 문제를 포함한 전체 개발 사이클을 모두 소유하는 교차 기능적 접근 방식을 채택하고 있습니다.
Prisma를 사용하면 DB 스키마와 객체 스키마를 함께 발전시킬 수 있으므로 애초에 편차의 필요성을 줄이면서도 @map
속성을 사용하여 애플리케이션과 데이터베이스를 어느 정도 분리된 상태로 유지할 수 있습니다. 이는 제한 사항처럼 보일 수 있지만, (객체 스키마를 통한) 도메인 모델의 진화가 데이터베이스에 사후에 부과되는 것을 방지할 수 있습니다.
Prisma의 Data Mapper 패턴의 구현이 기존의 Data Mapper ORM들과 개념적으로 어떻게 다른지를 이해하기 위해 간단하게 그들의 개념과 구성요소를 비교해봅시다.
개념 | 설명 | 전통적인 ORM에서 블록을 만드는 방법 | Prisma에서 블록을 만드는 방법 | Prisma에서의 단일 진실 공급원 |
---|---|---|---|---|
객체 스키마 | 애플리케이션의 인메모리 데이터 구조 | 모델 클래스 | 생성된 Typescript 타입들 | Prisma 스키마의 모델들 |
Data Mapper | 객체 스키마와 데이터베이스 간에 변환하는 코드 | 매퍼 클래스 | Prisma 클라이언트에서 만들어진 함수들 | Prisma 스키마의 @map 속성들 |
데이터베이스 스키마 | 데이터베이스의 테이블과 컬럼 같은 데이터 구조 | 프로그램적인 API나 손으로 작성된 SQL | Prisma Migrate로 작성된 SQL | Prisma 스키마 |
Prisma는 다음과 같은 추가적인 장점을 통해 Data Mapper 패턴에 부합합니다:
- Prisma 스키마를 기반으로 Prisma 클라이언트를 생성하여 클래스 및 매핑 로직을 정의하는 데 필요한 상용구를 줄입니다.
- 애플리케이션 객체와 데이터베이스 스키마 간의 동기화 문제를 제거합니다.
- 데이터베이스 마이그레이션은 Prisma 스키마에서 파생되기 때문에 일급 객체(First-class citizen)입니다.
이제 Data Mapper에 대한 Prisma 접근 방식의 개념에 대해 이야기했으니, 실제로 Prisma 스키마가 어떻게 작동하는지 살펴볼 수 있습니다.
Prisma 스키마
Prisma의 Data Mapper 패턴 구현의 핵심은 다음과 같은 단일 진실 공급원인 Prisma 스키마입니다:
- Prisma가 데이터베이스에 연결하는 방법을 구성함
- 애플리케이션 코드에서 사용하기 위한 타입-안전 ORM인 Prisma 클라이언트를 생성함
- Prisma Migrate로 데이터베이스 스키마 생성 및 발전
- 애플리케이션 객체와 데이터베이스 열 간의 매핑 정의.
Prisma의 모델은 Active Record ORM과는 약간 다른 의미입니다. Prisma에서 모델은 테이블, 관계, 열과 Prisma Client의 속성 간 매핑을 설명하는 추상 엔티티로 Prisma 스키마에 정의됩니다.
다음으로 블로그에 대한 Prisma 스키마를 예를 들어봅시다.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Post {
id Int @id @default(autoincrement())
title String
content String? @map("post_content")
published Boolean @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
위의 예는 다음과 같습니다:
datasource
블록은 데이터베이스에 대한 연결을 정의합니다.generator
블록은 Prisma가 TypeScript 및 Node.js용 Prisma 클라이언트를 생성하도록 지시합니다.Post
및User
모델은 데이터베이스 테이블에 매핑됩니다.- 두 모델은 각 사용자가 많은 관련 게시물을 가질 수 있는 일대다 관계를 갖습니다.
- 모델의 각 필드에는 유형이 있습니다(예:
id
의 유형은Int
) - 필드에는 정의할 필드 속성이 포함될 수 있습니다:
@id
속성이 있는 기본 키 (PK).@unique
속성이 있는 고유 키.- 기본값은
@default
속성을 사용합니다. - 맵 속성을 사용하여 테이블 열과 Prisma 클라이언트 필드 간 매핑(예:
content
필드(Prisma 클라이언트에서 액세스할 수 있음)는post_content
데이터베이스 열에 매핑됨).
User
/Post
관계는 다음 다이어그램으로 시각화할 수 있습니다.
Prisma 수준에서 User
/Post
관계는 다음으로 구성됩니다.
@relation
속성에 의해 참조되는 스칼라authorId
필드. 이 필드는 데이터베이스 테이블에 존재하며,Post
와User
를 연결하는 FK입니다.- 두 관계 필드인
author
와posts
는 데이터베이스 테이블에 존재하지 않습니다. 관계 필드는 Prisma 수준에서 모델 간의 연결을 정의하며, 관계에 접근하는 데에 사용되는 Prisma 스키마 및 생성된 Prisma 클라이언트에만 존재합니다.
Prisma 스키마의 선언적 특성은 간결하고, Prisma 클라이언트에서 데이터베이스 스키마 및 해당 표현을 정의할 수 있습니다.
Primsa 워크플로우
Prisma의 워크플로우는 전통적인 ORM과 조금 다릅니다. 새 애플리케이션을 처음부터 구축하거나 점진적으로 도입할 때 Prisma를 사용할 수 있습니다.
- 새 어플리케이션: 아직 데이터베이스 스키마가 없는 프로젝트는 Prisma 마이그레이션을 사용해서 데이터베이스 스키마를 생성할 수 있습니다.
- 기존 어플리케이션: 이미 데이터베이스 스키마가 있는 프로젝트는 Prisma를 사용해서 Prisma 스키마 및 Prisma 클라이언트를 생성할 수 있습니다. 이 사용 사례는 기존 마이그레이션 도구와 함께 작동하며 점진적 채택에 유용합니다. 마이그레이션 도구로 Prisma Migrate로 전환할 수 있습니다. 하지만 이는 선택 사항입니다.
두 워크플로 모두 Prisma 스키마가 기본 구성 파일입니다.
기존 데이터베이스가 있는 프로젝트에서 점진적 도입을 위한 워크플로우
기존 데이터베이스가 있는 프로젝트에는 일반적으로 이미 일부 데이터베이스 추상화 및 스키마가 있습니다. Prisma는 기존 데이터베이스를 인트로스펙팅하여 기존 데이터베이스 스키마를 반영하는 Prisma 스키마를 얻고 Prisma 클라이언트를 생성함으로써 이러한 프로젝트와 통합할 수 있습니다. 이 워크플로는 이미 사용 중인 모든 마이그레이션 도구 및 ORM과 호환됩니다. 점진적으로 평가하고 채택하는 것을 선호하는 경우, 이 접근 방식을 병렬 채택 전략의 일부로 사용할 수 있습니다.
이 워크플로우와 호환되는 설정의 일부는 다음과 같습니다.
CREATE TABLE
및ALTER TABLE
과 함께 일반 SQL 파일을 사용해서 데이터베이스 스키마를 만들고 변경하는 프로젝트db-migrate
또는Umzug
과 같은 타사 마이그레이션 라이브러리를 사용하는 프로젝트- 이미 ORM을 사용하고 있는 프로젝트. 이 경우, ORM을 통한 데이터베이스 접근은 변경되지 않고 생성된 Prisma Client를 점진적으로 적용할 수 있습니다.
다음은 기존 DB를 인트로스펙트하고 Prisma Client를 생성하는 데에 필요한 실제 단계들입니다.
datasource
와generator
를 정의하는schema.prisma
를 생성합니다.datasource db { provider = "postgresql" url = "postgresql://janedoe:janedoe@localhost:5432/hello-prisma" } generator client { provider = "prisma-client-js" }
prisma db pull
을 실행해서 데이터베이스 스키마에서 파생된 Prisma 스키마를 생성합니다.- (선택사항) Prisma 클라이언트와 데이터베이스 간의 필드 및 모델 매핑을 사용자 지정합니다.
prisma generate
를 실행합니다.
Prisma는 애플리케이션에서 가져올 수 있는 node_modules
폴더 내에 Prisma Client를 생성합니다. 더 자세한 설명은 Prisma Client API 문서를 참고하세요.
요약하자면, Prisma Client는 병렬 도입 전략의 일환으로 기존 데이터베이스 및 툴링이 있는 프로젝트에 통합할 수 있습니다. 새 프로젝트는 다음에 자세히 설명된 워크플로우를 사용합니다.
새 프로젝트를 위한 워크플로우
Prisma는 지원하는 워크플로우 측면에서 ORM과 다릅니다. 새 데이터베이스 스키마를 생성하고 변경하는 데 필요한 단계를 자세히 살펴보는 것은 Prisma 마이그레이션을 이해하는 데 유용합니다.
Prisma Migrate는 선언적 데이터 모델링 및 마이그레이션을 위한 CLI입니다. ORM의 일부로 제공되는 대부분의 마이그레이션 도구와 달리, 한 상태에서 다른 상태로 이동하는 작업 대신 현재 스키마만 설명하면 됩니다. Prisma Migrate는 연산을 추론하고 SQL을 생성하며 마이그레이션을 수행합니다.
이 예제는 위의 블로그 예제와 유사한 새 데이터베이스 스키마가 있는 새 프로젝트에서 Prisma를 사용하는 방법을 보여줍니다.
- Prisma 스키마를 생성합니다.
// schema.prisma datasource db { provider = "postgresql" url = "postgresql://janedoe:janedoe@localhost:5432/hello-prisma" } generator client { provider = "prisma-client-js" } model Post { id Int @id @default(autoincrement()) title String content String? @map("post_content") published Boolean @default(false) author User? @relation(fields: [authorId], references: [id]) authorId Int? } model User { id Int @id @default(autoincrement()) email String @unique name String? posts Post[] }
prisma migrate
를 실행해서 마이그레이션을 위한 SQL를 생성해서 데이터베이스에 적용한 후 Prisma Client를 생성합니다.
이상의 추가적인 데이터베이스 스키마의 변경을 위해서는 Prisma 스키마를 변경하고 prisma migrate
를 다시 실행합니다.
마지막 단계에서는 Prisma 스키마에 필드를 추가하고 Prisma 마이그레이션을 사용하여 데이터베이스 스키마를 원하는 상태로 변환함으로써 선언적 마이그레이션이 어떻게 작동하는지 보여줍니다. 마이그레이션이 실행되면 업데이트된 스키마를 반영하도록 Prisma 클라이언트가 자동으로 다시 생성됩니다.
Prisma 마이그레이션을 사용하지 않으면서도 새 프로젝트에서 유형 안전 생성된 Prisma 클라이언트를 사용하려면 다음 섹션을 참조하세요.
Prisma Migrate 없이 새 프로젝트를 만들기 위한 대안
새 프로젝트에서 Prisma 마이그레이트 대신 타사 마이그레이션 도구와 함께 Prisma 클라이언트를 사용할 수 있습니다. 예를 들어, 새 프로젝트에서 데이터베이스 스키마 및 마이그레이션을 생성하는 데는 Node.js 마이그레이션 프레임워크 db-migrate를 사용하고 쿼리에는 Prisma Client를 사용하도록 선택할 수 있습니다. 이는 본질적으로 기존 데이터베이스의 워크플로에서 다룹니다.
Prisma Client로 데이터에 접근하기
지금까지 Prisma의 개념, Data Mapper 패턴의 구현, 그리고 Prisma가 지원하는 워크플로우에 대해 살펴보았습니다. 이 마지막 섹션에서는 Prisma Client를 사용하여 애플리케이션의 데이터에 액세스하는 방법을 살펴봅니다.
Prisma Client로 데이터베이스에 액세스하는 것은 데이터베이스에 노출된 쿼리 메서드를 통해 이루어집니다. 모든 쿼리는 일반 자바스크립트 객체를 반환합니다. 위의 블로그 스키마가 주어졌을 때, User
를 가져오는 것은 다음과 같습니다.
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const user = await prisma.user.findUnique({
where: {
email: 'alice@prisma.io',
},
})
이 쿼리에서 findUnique 메서드는 User 테이블에서 단일 행을 가져오는 데 사용됩니다. 기본적으로 Prisma는 User
테이블의 모든 스칼라 필드를 반환합니다.
export type User = {
id: number
email: string
name: string | null
}
이렇게 하면 존재하지 않는 필드에 액세스하면 타입 오류가 발생합니다. 더 넓게는 쿼리를 실행하기 전에 모든 쿼리의 결과 유형을 미리 알 수 있으므로 오류를 포착하는 데 도움이 됩니다. 예를 들어 다음 코드는 타입 오류를 발생시킵니다.
console.log(user.lastName) // Property 'lastName' does not exist on type 'User'.
연관관계 가져오기
Prisma Client로 연관관계를 가져오는 것은 include
옵션을 통해 이루어집니다. 예를 들어 사용자와 그 사용자의 글들을 가져오는 것은 다음과 같이 이루어집니다.
const user = await prisma.user.findUnique({
where: {
email: 'alice@prisma.io',
},
include: {
posts: true,
},
})
이 쿼리로, user
의 타입은 posts
배열 필드를 가지게 됩니다.
console.log(user.posts[0].title)
이 예제는 CRUD 작업을 위한 Prisma Client의 API의 표면적인 모습일 뿐이며, 자세한 내용은 문서에서 확인할 수 있습니다. 주요 아이디어는 모든 쿼리와 결과가 유형별로 뒷받침되며 연관관계를 가져오는 방법을 완전히 제어할 수 있다는 것입니다.
결론
요약하자면, Prisma는 기존 ORM과는 다른 새로운 종류의 Data Mapper ORM으로, 기존 ORM과 관련된 일반적인 문제를 겪지 않습니다.
기존 ORM과 달리 Prisma를 사용하면 데이터베이스 스키마 및 애플리케이션 모델에 대한 선언적 단일 진실 공급원인 Prisma 스키마를 정의할 수 있습니다. Prisma Client의 모든 쿼리는 일반 JavaScript 객체를 반환하므로 데이터베이스와 상호 작용하는 프로세스가 훨씬 더 자연스럽고 예측 가능합니다.
Prisma는 새 프로젝트를 시작하거나 기존 프로젝트에 채택하기 위한 두 가지 주요 워크플로를 지원합니다. 두 워크플로우 모두 Prisma 스키마가 기본 구성 파일입니다.
모든 추상화와 마찬가지로, Prisma와 다른 ORM은 서로 다른 가정을 통해 데이터베이스의 기본 세부 사항 중 일부를 숨깁니다.
이러한 차이점과 사용 사례는 모두 워크플로와 도입 비용에 영향을 미칩니다. 차이점을 이해하면 정보에 입각한 결정을 내리는 데 도움이 되길 바랍니다.
'라이브러리 프레임워크 > Prisma ORM' 카테고리의 다른 글
[번역] Prisma가 기존의 '전통적인' ORM보다 더 나을까요? (0) | 2023.08.01 |
---|---|
[번역] TypeORM vs Prisma (0) | 2023.07.25 |