ORM
프리즈마는 ORM(Object Relational Mapping)입니다. 하지만 프리즈마는 전통적인 ORM과 근본적으로 다르며 기존의 ORM에서 많이 발생하는 문제가 없는 새로운 종류의 ORM입니다.
전통적인 ORM은 테이블을 프로그래밍 언어의 모델 클래스에 매핑하여 관계형 데이터베이스 작업을 위한 객체 지향 방식을 제공합니다. 이 접근 방식은 객체-관계형 임피던스 불일치로 인해 많은 문제를 야기합니다.
프리즈마는 기존 ORM과 근본적으로 다릅니다. 프리즈마를 사용하면 데이터베이스 스키마와 프로그래밍 언어의 모델에 대한 단일 진실 공급원(single source of truth, SSOT) 역할을 하는 선언적 프리즈마 스키마에서 모델을 정의할 수 있습니다. 그런 다음 앱의 코드에서 프리즈마 클라이언트를 사용하여 복잡한 모델 인스턴스를 관리하는 오버헤드 없이 안전한 타입 방식으로 데이터베이스의 데이터를 읽고 쓸 수 있습니다. 프리즈마 클라이언트가 항상 순수한 자바스크립트 객체를 반환하기 때문에 데이터 질의 프로세스를 훨씬 더 자연스럽고 예측 가능하게 만듭니다.
이제 ORM 패턴과 작업 흐름, 프리즈마가 데이터 매퍼 패턴을 구현하는 방법, 프리즈마 접근 방식의 이점에 대해 자세히 알아볼 것입니다.
ORM이 무엇인가요?
ORM 패턴 - 액티브 레코드와 데이터 매퍼
ORM은 높은 수준의 데이터베이스 추상화를 제공합니다. ORM은 데이터베이스의 복잡성을 숨기면서 데이터를 생성, 읽기, 삭제, 조작하기 위해 객체를 통한 프로그래밍 방식의 인터페이스를 제공합니다.
ORM의 아이디어는 모델을 데이터베이스의 테이블에 매핑하는 클래스로 정의한다는 것입니다. 클래스와 인스턴스는 데이터베이스에서 데이터를 읽고 쓸 수 있는 프로그래밍 방식의 API를 제공합니다.
두 가지 일반적인 ORM 패턴이 있습니다. 액티브 레코드와 데이터 매퍼는 객체와 데이터베이스 간에 데이터를 전송하는 방식이 다릅니다. 두 패턴 모두 클래스를 기본 구성 요소로 정의해야 합니다. 두 패턴의 가장 큰 차이점은 다음과 같습니다. 데이터 매퍼 패턴은 데이터베이스에서 앱 코드의 메모리 내 객체를 분리하고 데이터 매퍼 계층을 사용하여 둘 사이에서 데이터를 전송합니다. 실제로 데이터 매퍼를 사용하면 메모리 내 객체(데이터베이스의 데이터를 표현)는 데이터베이스의 존재조차 모릅니다.
액티브 레코드
액티브 레코드 ORM은 두 표현의 구조가 밀접하게 관련된 데이터베이스 테이블에 모델 클래스를 매핑합니다. 예를 들어 모델 클래스의 각 필드는 데이터베이스 테이블에서 일치하는 열을 갖습니다. 모델 클래스의 인스턴스는 데이터베이스 행을 래핑하고 데이터베이스의 지속적인 변경을 처리하기 위해 데이터와 접근 논리를 모두 전달합니다. 또한 모델 클래스는 모델의 데이터와 관련된 업무 논리를 전달할 수 있습니다.
모델 클래스에는 일반적으로 다음을 수행하는 메서드가 있습니다.
- SQL 질의에서 모델의 인스턴스를 생성함
- 나중에 테이블에 삽입할 수 있도록 새 인스턴스를 생성함
- 일반적으로 사용되는 SQL 질의를 래핑하고 액티브 레코드 개체를 반환함
- 데이터베이스를 갱신하고 액티브 레코드에 데이터를 삽입함
- 필드를 가져오고 설정함
- 업무 논리를 구현함
데이터 매퍼
데이터 매퍼 ORM은 액티브 레코드와 대조적으로 데이터베이스의 표현에서 앱의 메모리 내 데이터 표현을 분리합니다. 매핑 책임을 두 가지 유형의 클래스로 분리하여 디커플링을 달성합니다.
- 개체(entity) 클래스 - 데이터베이스를 모르는 개체에 대한 응용 프로그램의 메모리 내 표현
- 매퍼 클래스 - 다음 두 가지 책임을 가짐
- 두 표현 간의 데이터 변환
- 데이터베이스에서 데이터를 가져오고 데이터베이스의 변경 사항을 유지하는 데 필요한 SQL을 생성함
데이터 매퍼 ORM을 사용하면 코드에 구현된 문제 영역과 데이터베이스 간의 유연성을 높일 수 있습니다. 데이터 매퍼 패턴을 사용하면 전체 데이터 매핑 계층 뒤에 있는 도메인을 알 필요가 없고 데이터베이스의 구현 방식을 숨길 수 있기 때문입니다.
전통적인 데이터 매퍼 ORM이 이렇게 하는 이유 중 하나는 두 가지 책임이 별도의 팀(예: DBA와 백엔드 개발자)에 의해 처리되는 조직 구조 때문입니다.
실제로 모든 데이터 매퍼 ORM이 이 패턴을 엄격하게 준수하는 것은 아닙니다. 예를 들어 액티브 레코드와 데이터 매퍼를 모두 지원하는 타입스크립트 환경 시스템에서 널리 사용되는 ORM인 타입ORM은 데이터 매퍼에 대해 다음과 같은 접근 방식을 취합니다.
- 개체 클래스는 데코레이터(
@Column
)를 사용하여 클래스 속성을 테이블 열에 매핑하고 데이터베이스를 인식합니다. - 매퍼 클래스 대신 저장소 클래스가 데이터베이스 질의에 사용되며 사용자 지정 질의가 포함될 수 있습니다. 저장소는 데코레이터를 사용하여 개체 속성과 데이터베이스 열 간의 매핑을 결정합니다.
데이터베이스에 다음 User
테이블이 있다고 가정해 보겠습니다.
대응되는 개체 클래스는 다음과 같습니다.
ts
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;}
ts
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문 또는 (ActiveRecord나 SQLAlchemy와 같이) SQL문으로 변환되는 프로그래밍 코드의 형태일 수 있습니다.
데이터베이스에는 일반적으로 데이터가 포함되어 있습니다. 마이그레이션을 사용하면 스키마 변경 사항을 작은 단위로 나눠 우발적인 데이터 손실을 방지할 수 있습니다.
프로젝트를 처음부터 시작한다고 가정하면 전체 작업 흐름은 다음과 같습니다.
먼저 데이터베이스 스키마에 User
테이블을 만들고 위의 예시에 있는 User
개체 클래스를 정의하는 마이그레이션을 만듭니다. 프로젝트가 진행되어 테이블에 새 salutation
열을 추가하기로 결정했다면 User
테이블을 변경하고 salutation
열을 추가하는 또 다른 마이그레이션을 생성합니다.
타입ORM으로 마이그레이션을 작성하면 다음과 같습니다.
ts
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"`);}}
ts
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
열을 고려하여 개체 및 매퍼 클래스도 갱신해야 합니다.
타입ORM에서는 User
개체 클래스에 salutation
프로퍼티를 추가하면 됩니다.
ts
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;}
ts
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에서 어려울 수 있습니다. 이미 존재하는 열의 이름을 바꾸는 것은 훨씬 더 복잡하며 열에 대한 참조를 검색하고 교체해야 합니다.
요약하면 스키마를 발전시키는 것은 앱 구축의 핵심 부분입니다. ORM의 경우, 스키마 갱신 작업 흐름에는 마이그레이션 도구를 사용하여 마이그레이션을 만든 다음 해당 개체와 매퍼 클래스를 갱신하는 작업이 포함됩니다(구현에 따라 다름). 나중에 배우겠지만 프리즈마는 이에 대해 다른 접근 방식을 취합니다.
지금까지 마이그레이션이 무엇이며 개발 작업 흐름에 어떻게 적용되는지 살펴보았습니다. 이제 ORM의 장점과 단점에 대해 알아보겠습니다.
ORM의 장점
개발자가 ORM을 사용하는 데에는 여러 가지 이유가 있습니다.
- ORM은 도메인 모델 구현을 용이하게 합니다. 도메인 모델은 업무 논리의 동작과 데이터를 통합하는 개체 모델입니다. 즉, 데이터베이스 구조나 SQL 시맨틱이 아닌 실제 업무 개념에 집중할 수 있습니다.
- ORM은 코드 양을 줄이는 데 도움이 됩니다. 일반적인 CRUD 작업에 대한 반복적인 SQL문을 작성할 필요가 없습니다. 그리고 SQL 주입과 같은 취약점을 막기 위해 사용자 입력을 이스케이핑할 필요가 없습니다.
- ORM을 사용하면 SQL을 거의 작성하지 않아도 됩니다(복잡성에 따라 이상한 원시 질의를 작성해야 할 수도 있음). 이는 SQL에 익숙하지 않지만 데이터베이스 작업을 원하는 개발자에게 유용합니다.
- 많은 ORM은 데이터베이스별 세부 정보를 추상화합니다. 이는 이론적으로 기존 데이터베이스에서 다른 데이터베이스로의 변경을 쉽게 만듭니다. 하지만 실제로는 앱의 데이터베이스를 변경하는 일은 거의 없습니다.
생산성 향상을 목표로 하는 모든 추상화와 마찬가지로 ORM 사용에도 단점이 있습니다.
ORM의 단점
ORM을 사용하기 시작할 때는 단점이 잘 드러나지 않습니다. 여기서는 일반적인 단점을 다룹니다.
- ORM을 사용하면 데이터베이스 테이블의 객체 그래프 표현을 형성하여 객체-관계형 임피던스 불일치를 초래할 수 있습니다. 이는 해결하려는 문제가 관계형 데이터베이스에 쉽게 매핑되지 않는 복잡한 객체 그래프를 형성할 때 발생합니다. 관계형 데이터베이스와 메모리 내(객체 포함)에 있는 데이터 표현 간의 동기화는 매우 어렵습니다. 객체가 관계형 데이터베이스 레코드에 비해 서로 연관될 수 있는 방식이 더 유연하고 다양하기 때문입니다.
- ORM이 문제와 관련된 복잡성을 처리하는 동안에도 동기화 문제는 사라지지 않습니다. 데이터베이스 스키마나 데이터 모델을 변경하려면 변경 사항을 다시 다른 쪽에 매핑해야 합니다. 이 부담은 종종 개발자에게 있습니다. 프로젝트를 수행하는 팀의 맥락에서 데이터베이스 스키마 변경에는 조정이 필요합니다.
- ORM은 캡슐화 복잡성으로 인해 API 표면이 넓어지는 경향이 있습니다. SQL을 작성하지 않아도 되는 반면 ORM 사용법을 배우는 데 많은 시간이 들어갑니다. 이는 대부분의 추상화에 적용되지만 데이터베이스 작동 방식을 이해하지 못하면 느린 질의를 개선하기 어려울 수 있습니다.
- 일부 복잡한 질의는 SQL이 제공하는 유연성으로 인해 ORM에서 지원되지 않습니다. 이 문제는 ORM에 SQL문 문자열을 전달하고 질의를 자동으로 실행하는 원시 SQL 질의 기능으로 해결 가능합니다.
프리즈마
프리즈마는 앱 개발자의 데이터베이스 작업을 돕고 다음 도구를 제공하는 차세대 ORM입니다.
- 프리즈마 클라이언트 - 앱에서 사용할 수 있게 자동 생성되며 타입이 안전한 데이터베이스 클라이언트
- 프리즈마 마이그레이트 - 선언적 데이터 모델링 및 마이그레이션 도구
- 프리즈마 스튜디오 - 데이터베이스의 데이터를 검색하고 관리하기 위한 모던 GUI
프리즈마 클라이언트는 가장 눈에 띄는 도구이기 때문에 간단히 프리즈마라고 부르기도 합니다.
이 세 도구는 데이터베이스 스키마, 앱의 객체 스키마, 둘 사이의 매핑에 대한 단일 진실 공급원으로 프리즈마 스키마를 사용합니다. 프리즈마 스키마는 개발자에 의해 정의되며 프리즈마의 주요 설정 파일입니다.
프리즈마는 타입 안전성, 풍부한 자동 완성, 관계 가져오기 API 같은 기능을 사용하여 개발 중인 소프트웨어의 생산성과 신뢰성을 향상시킵니다.
프리즈마가 데이터 매퍼 패턴을 구현하는 방법
앞에서 언급했듯이 데이터 매퍼 패턴은 데이터베이스와 앱을 서로 다른 팀에서 소유하고 있는 조직과 잘 어울립니다.
관리형 데이터베이스 서비스 및 데브옵스(DevOps) 방식이 포함된 최신 클라우드 환경의 등장으로 더 많은 팀이 교차 기능 접근법을 채택하고 있습니다.
프리즈마를 사용하면 DB 스키마와 객체 스키마를 동시에 발전시킬 수 있으므로 처음부터 편차를 줄이면서 @map
속성을 사용하여 앱과 데이터베이스를 어느 정도 분리된 상태로 유지할 수 있습니다. 제한처럼 보일 수 있지만, 이는 도메인 모델의 진화(객체 스키마를 통한)가 나중에 데이터베이스에 부과되는 것을 막습니다.
프리즈마의 데이터 매퍼 패턴 구현이 기존 데이터 매퍼 ORM과 개념적으로 어떻게 다른지 이해하기 위해 개념과 구성 요소를 간략하게 비교해 보겠습니다.
개념 | 설명 | 기존 ORM의 구성 요소 | 프리즈마의 구성 요소 | 프리즈마의 믿을 만한 소스 |
---|---|---|---|---|
객체 스키마 | 앱의 인메모리 데이터 구조 | 모델 클래스 | 생성된 타입스크립트 타입 | 프리즈마 스키마의 모델 |
데이터 매퍼 | 객체 스키마와 데이터베이스 사이를 변환하는 코드 | 매퍼 클래스 | 프리즈마 클라이언트에서 생성된 함수 | 프리즈마 스키마의 @map 속성 |
데이터베이스 스키마 | 데이터베이스의 데이터 구조(예: 테이블과 열) | 직접 작성하거나 프로그래밍 방식 API를 사용하여 작성한 SQL | 프리즈마 마이그레이트에 의해 생성된 SQL | 프리즈마 스키마 |
프리즈마는 다음 추가 이점과 함께 데이터 매퍼 패턴을 구현합니다.
- 프리즈마 스키마를 기반으로 프리즈마 클라이언트를 생성하여 클래스 정의 및 매핑 논리의 상용구를 줄임
- 앱 객체와 데이터베이스 스키마 간의 동기화 문제를 제거함
- 데이터베이스 마이그레이션은 프리즈마 스키마에서 파생되었기 때문에 일급 시민임
프리즈마 스키마
프리즈마의 데이터 매퍼 패턴 구현의 중심에는 프리즈마 스키마가 있습니다. 프리즈마 스키마는 다음 책임에 대한 단일 진실 공급원입니다.
- 프리즈마가 데이터베이스를 연결하는 방법을 설정함
- 프리즈마 클라이언트 생성 – 앱 코드에서 사용하기 위한 타입 안전 ORM
- 프리즈마 마이그레이트로 데이터베이스 스키마를 생성하고 개선함
- 앱 객체와 데이터베이스 열 간의 매핑을 정의함
프리즈마의 모델은 액티브 레코드 ORM과 의미가 조금 다릅니다. 프리즈마에서 모델은 프리즈마 클라이언트의 속성에 대한 열 간의 매핑, 관계, 테이블을 설명하는 추상 개체로 프리즈마 스키마에 정의됩니다.
예를 들어 블로그에 대한 프리즈마 스키마는 다음과 같습니다.
prisma
datasource db {provider = "postgresql"url = env("DATABASE_URL")}generator client {provider = "prisma-client-js"}model Post {id Int @id @default(autoincrement())title Stringcontent 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 @uniquename String?posts Post[]}
prisma
datasource db {provider = "postgresql"url = env("DATABASE_URL")}generator client {provider = "prisma-client-js"}model Post {id Int @id @default(autoincrement())title Stringcontent 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 @uniquename String?posts Post[]}
위의 예시를 분석하면 다음과 같습니다.
datasource
블록은 데이터베이스에 대한 연결을 정의합니다.generator
블록은 프리즈마에게 타입스크립트 및 노드용 클라이언트를 생성하도록 지시한다 .Post
와User
모델은 데이터베이스 테이블에 매핑됩니다.- 두 모델은 각
User
가 많은 관련Post
를 가질 수 있는 1-n 관계를 갖습니다. - 모델의 각 필드에는 타입이 있습니다. 예를 들어
id
는Int
타입을 갖습니다. - 필드에는 다음을 정의하는 필드 속성이 포함될 수 있습니다.
@id
속성이 있는 주 키@unique
속성이 있는 고유 키@default
속성이 있는 기본값@map
속성이 있는 프리즈마 클라이언트 필드와 테이블 열 간의 매핑. 예를 들어content
필드(프리즈마 클라이언트에서 액세스 가능)가 데이터베이스의post_content
열에 매핑됩니다.
User
-Post
관계를 시각화한 다이어그램은 다음과 같습니다.
프리즈마 수준에서 User
-Post
관계는 다음으로 구성됩니다.
@relation
속성에서 참조하는 스칼라authorId
필드입니다. 이 필드는 데이터베이스 테이블에 존재합니다.Post
와User
를 연결하는 외래 키입니다.- 두 개의 관계 필드 -
author
와posts
는 데이터베이스 테이블에 존재하지 않습니다. 관계 필드는 프리즈마 수준에서 모델 간의 연결을 정의합니다. 프리즈마 스키마와 생성된 프리즈마 클라이언트에만 존재하며 관계에 접근하는 데 사용됩니다.
프리즈마 스키마의 선언적 특성은 간결하며 프리즈마 클라이언트에서 데이터베이스 스키마와 해당 표현을 정의할 수 있습니다.
프리즈마 작업 흐름
프리즈마의 작업 흐름은 기존 ORM과 약간 다릅니다. 새로운 앱을 처음부터 구축하거나 점진적으로 채택할 때 프리즈마를 사용할 수 있습니다.
- 신규 애플리케이션(그린필드) - 아직 데이터베이스 스키마가 없는 프로젝트는 프리즈마 마이그레이트를 사용하여 데이터베이스 스키마를 생성할 수 있습니다.
- 기존 애플리케이션(브라운필드) - 이미 데이터베이스 스키마가 있는 프로젝트를 프리즈마에서 분석하여 프리즈마 스키마와 프리즈마 클라이언트를 생성할 수 있습니다. 이 사용 사례는 기존 마이그레이션 도구와 함께 작동하며 점진적 채택에 유용합니다. 마이그레이션 도구로 프리즈마 마이그레이트로 전환하는 것이 가능합니다. 그러나 이것은 선택 사항입니다.
두 작업 흐름 모두에서 프리즈마 스키마가 주요 설정 파일입니다.
기존 데이터베이스가 있는 프로젝트에서 점진적 채택을 하는 작업 흐름
브라운필드 프로젝트에는 일반적으로 이미 데이터베이스 추상화와 스키마가 있습니다. 프리즈마는 기존 데이터베이스를 검사하여 기존 데이터베이스 스키마를 반영하는 프리즈마 스키마를 얻고 프리즈마 클라이언트를 생성함으로써 해당 프로젝트와 통합할 수 있습니다. 이 작업 흐름은 이미 사용 중인 모든 마이그레이션 도구 및 ORM과 호환됩니다. 점진적인 평가 및 채택을 선호하는 경우에는 이 방식을 병렬 채택 전략의 일부로 사용할 수 있습니다.
이 작업 흐름과 호환되는 설정의 전체 목록은 다음과 같습니다.
- 데이터베이스 스키마를 생성하고 변경하기 위해
CREATE TABLE
과ALTER TABLE
로 이루어진 순수 SQL 파일을 사용하는 프로젝트 - db-migrate 또는 Umzug와 같은 타사 마이그레이션 라이브러리를 사용하는 프로젝트
- 이미 ORM을 사용 중인 프로젝트. 이 경우에는 ORM을 통한 데이터베이스 접근은 변경되지 않고 생성된 프리즈마 클라이언트가 점진적으로 채택될 수 있음
실제로 기존 DB를 점검하고 프리즈마 클라이언트를 생성하는 데 필요한 단계는 다음과 같습니다.
datasource
(이 경우에는 기존 DB)와generator
를 정의하는schema.prisma
을 생성합니다.prismadatasource db {provider = "postgresql"url = "postgresql://janedoe:janedoe@localhost:5432/hello-prisma"}generator client {provider = "prisma-client-js"}prismadatasource db {provider = "postgresql"url = "postgresql://janedoe:janedoe@localhost:5432/hello-prisma"}generator client {provider = "prisma-client-js"}prisma db pull
을 실행하여 데이터베이스 스키마에서 파생된 모델로 프리즈마 스키마를 채웁니다.(선택 사항) 프리즈마 클라이언트와 데이터베이스 간의 필드 및 모델 매핑을 사용자 정의합니다.
prisma generate
를 실행합니다.
프리즈마는 node_modules
폴더 안에 프리즈마 클라이언트를 생성하여 앱에서 가져오는 것이 가능해집니다. 자세한 사용 설명서는 프리즈마 클라이언트 API 문서를 참고합니다.
요약하자면 프리즈마 클라이언트는 병렬 채택 전략의 일부로 기존 데이터베이스와 도구를 사용하여 프로젝트에 통합할 수 있습니다.
새 프로젝트의 작업 흐름
프리즈마는 지원하는 작업 흐름 측면에서 ORM과 다릅니다. 새 데이터베이스 스키마를 만들고 변경하는 데 필요한 단계를 따라가면 프리즈마 마이그레이트를 이해하는 데 도움이 됩니다.
프리즈마 마이그레이트는 선언적 데이터 모델링과 마이그레이션을 위한 CLI입니다. ORM의 일부로 제공되는 대부분의 마이그레이션 도구와 달리 다른 상태로 바꾸는 작업을 할 필요가 없습니다. 대신 현재 스키마만 묘사하면 됩니다. 프리즈마 마이그레이트는 작업을 추론하고 SQL을 생성하며 마이그레이션을 수행합니다.
다음 예시는 위의 블로그 예시와 유사한 새 데이터베이스 스키마가 있는 새 프로젝트에서 프리즈마를 사용하는 방법을 보여줍니다.
프리즈마 스키마를 생성합니다.
schema.prismaprismadatasource 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 Stringcontent 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 @uniquename String?posts Post[]}schema.prismaprismadatasource 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 Stringcontent 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 @uniquename String?posts Post[]}prisma migrate
를 실행하여 마이그레이션을 위한 SQL을 생성하며, 이를 데이터베이스에 적용하고 프리즈마 클라이언트를 생성합니다.
데이터베이스 스키마에 추가 변경 사항이 생기면 다음을 수행합니다.
- 프리즈마 스키마에 변경 사항을 적용합니다.
User
모델에registrationDate
필드를 추가하는 것이 그 예입니다. prisma migrate
을 다시 실행합니다.
마지막 단계는 프리즈마 스키마에 필드를 추가하고 프리즈마 마이그레이트를 사용하여 데이터베이스 스키마를 원하는 상태로 변환하는 선언적 마이그레이션이 작동하는 방식을 보여줍니다. 마이그레이션이 실행된 후 프리즈마 클라이언트는 갱신된 스키마를 반영하도록 자동으로 재생성됩니다.
프리즈마 마이그레이트를 사용하지 않는 새 프로젝트를 위한 대안
프리즈마 마이그레이트 대신 타사 마이그레이션 도구로 새 프로젝트에서 프리즈마 클라이언트를 사용할 수 있습니다.
예를 들어 새 프로젝트는 노드 마이그레이션 프레임워크 db-migrate를 사용하여 데이터베이스 스키마 및 마이그레이션을 생성하고 프리즈마 클라이언트를 질의에 사용할 수 있습니다. 자세한 내용은 기존 데이터베이스가 이미 존재하는 작업 흐름를 확인하세요.
프리즈마 클라이언트로 데이터 접근하기
프리즈마 클라이언트에 표시되는 질의 메서드를 사용하여 데이터베이스에 접근할 수 있습니다. 모든 질의는 오래된 순수 자바스크립트 객체를 반환합니다.
위의 블로그 스키마에 대해 사용자를 가져오는 코드는 다음과 같습니다.
ts
import { PrismaClient } from '@prisma/client';const prisma = new PrismaClient();const user = await prisma.user.findUnique({where: {email: 'alice@prisma.io',},});
ts
import { PrismaClient } from '@prisma/client';const prisma = new PrismaClient();const user = await prisma.user.findUnique({where: {email: 'alice@prisma.io',},});
이 질의에서 findUnique
메서드는 User
테이블에서 하나의 행을 가져옵니다. 기본적으로 프리즈마는 User
테이블의 모든 스칼라 필드를 반환합니다.
예시에서는 프리즈마 클라이언트에서 제공하는 타입 안전 기능을 최대한 활용하기 위해 타입스크립트를 사용합니다. 그러나 프리즈마는 노드의 자바스크립트에서도 잘 작동합니다.
프리즈마 클라이언트는 프리즈마 스키마에서 코드를 생성하여 질의와 결과를 구조적 타입에 매핑합니다. 이는 생성된 프리즈마 클라이언트에 연결된 타입이 user
에 있음을 의미합니다.
ts
export type User = {id: number;email: string;name: string | null;};
ts
export type User = {id: number;email: string;name: string | null;};
이렇게 하면 존재하지 않는 필드에 접근할 때 타입 오류가 발생합니다. 더 나아가 모든 질의에 대한 결과 타입을 질의를 실행하기 전에 미리 알 수 있어 오류를 잡는 데 도움이 됩니다.
예를 들어 다음 코드는 타입 오류를 발생시킵니다.
ts
console.log(user.lastName); // lastName 프로퍼티는 User 타입에 존재하지 않습니다.
ts
console.log(user.lastName); // lastName 프로퍼티는 User 타입에 존재하지 않습니다.
관계 가져오기
프리즈마 클라이언트에서는 include
옵션으로 관계를 가져올 수 있습니다.
예를 들어 사용자와 사용자의 게시물을 가져오는 코드는 다음과 같습니다.
ts
const user = await prisma.user.findUnique({where: {email: 'alice@prisma.io',},include: {posts: true,},});
ts
const user = await prisma.user.findUnique({where: {email: 'alice@prisma.io',},include: {posts: true,},});
이 질의를 사용하면 user
의 타입에는 posts
배열 필드에 접근할 수 있는 Post
도 포함됩니다.
ts
console.log(user.posts[0].title);
ts
console.log(user.posts[0].title);
프리즈마 클라이언트 API의 CRUD 작업에 대한 자세한 내용은 문서에서 확인할 수 있습니다. 중요한 것은 모든 질의와 결과가 타입별로 지원되며 관계를 가져오는 방법을 완전히 제어할 수 있다는 것입니다.
결론
프리즈마는 전통적인 ORM과 다르며 기존 ORM에서 일반적으로 발생하는 문제가 없는 새로운 종류의 데이터 매퍼 ORM입니다.
기존 ORM과 달리 프리즈마를 사용하면 데이터베이스 스키마 및 앱 모델에 대한 선언적 단일 진실 공급원인 프리즈마 스키마를 정의할 수 있습니다. 프리즈마 클라이언트의 모든 질의는 일반 자바스크립트 객체를 반환하므로 데이터베이스와 상호 작용하는 프로세스가 훨씬 더 자연스럽고 예측 가능해집니다.
프리즈마는 새로운 프로젝트를 시작하고 기존 프로젝트에 적용하기 위한 두 가지 주요 작업 흐름을 지원합니다. 두 작업 흐름 모두에서 프리즈마 스키마는 주요 설정 파일입니다.
모든 추상화와 마찬가지로 프리즈마를 비롯한 ORM은 서로 다른 가정으로 기반 데이터베이스의 세부 정보를 숨깁니다.
이러한 차이점과 사용 사례는 모두 작업 흐름과 채택 비용에 영향을 미칩니다. 이 차이를 이해하면 정보에 근거한 결정을 내리는 데 도움이 될 것입니다.