[Prisma] Prisma란?

koline·2023년 11월 14일

prisma

목록 보기
1/6

Prisma


Prisma 공식 문서에서는 Prisma를 다음과 같이 소개한다.

차세대 Open Source ORM으로써 TypeScript와 NodeJS를 위한 type-safe하고 자동화된 쿼리 빌더이다.

REST API, GraphQL API, gRPC API 등 NodeJS의 대부분의 백엔드 프레임워크/라이브러리뿐 아니라 RDBMS, Mongo DB 등 다양한 DBMS와 연동이 가능하다(버전 확인 필요).

특히 Prisma 자체가 창업자가 GraphQL 개발자와 협업하여 개발한 기술이기 때문에 GraphQL과의 궁합이 좋다

차세대 ORM이라고 자칭하는 이유는 기존의 ORM과 달리 쿼리 없이 prisma-client의 내장 함수로 완전히 작동이 가능하기 때문인듯하다. 예를 들어 Java의 JPA의 경우 JPA Query Factory나 EntityManager를 통해 쿼리를 보내지만 복잡한 쿼리나 커스텀이 필요한 경우 JPQL, QueryDSL 등 직접 쿼리를 작성하여 쿼리를 생성하는데, Prisma는 이 과정을 없앰으로써 기존의 ORM보다 개발자의 생산성을 높이면서 동시에 쿼리에 대한 제어는 더 높다고 주장한다.

여기서 말하는 제어가 높다는 건 ORM의 본질적인 문제인 복잡한 쿼리의 사용이나 SQL의 함수를 사용할 때의 제약을 말하는데, 솔직히 Prisma가 기존 NodeJS에서 많이 사용하는 Sequelize나 TypeORM에 비교하여 더 높은 제어권을 가질 수 있는지는 크게 와닿지는 않는다. 다만 TypeORM의 대표적인 문제로 꼽히는 타입체크의 부재(AnyORM이라고 불리는 이유)나 칼럼을 string으로 정의해 자동완성이나 오타체크가 안되는 부분에서는 Prisma가 훨씬 사용하기 편하기는 하지만, 본질적으로 ORM이 가지고 있는 한계는 Prisma에서도 동일한 듯 하다.

또한 timezone을 지원하지 않아 수동으로 시차를 맞춰줘야 한다거나하는 매우 큰 사소한 문제가 있다.



사용하는 이유


하지만 기존 ORM과 비교해서 명확한 장점도 있다.

1. 근본적으로 ORM과 다른 작동방식

Prisma가 자신을 차세대 ORM이라고 주장하는 이유이다. Prisma 공식 문서에서도 확인할 수 있듯이 기존의 ORM은 DB의 테이블과 코드 내의 클래스를 매핑하여 사용하는 반면, Prisma는 schema.prisma 파일에서 생성한 model이 유일한 source of truth이기 때문에 Data Mapper 패턴에서 메모리상의 객체와 DB의 결합이 풀리고 데이터 전송을 Data Mapper가 전담하게 된다.

이 과정에서 Entity class, Mapper class 두 개의 클래스가 관여하게 된다

  • Entity class: Application의 메모리상에 존재하는 객체로 DB와는 연관이 없다
  • Mapper class: DB-메모리상의 객체 사이에서 쿼리를 생성하고 데이터를 전송한다

사실 Sequelize나 TypeORM을 사용할 때도 model을 작성해서 migrate하는건 똑같기 때문에 실질적인 차이가 정말 있는지 헷갈리는데 Prisma 공식 문서에 따르면 기존의 ORM은 @Column 어노테이션 등을 사용하여 DB와 연결하기 때문에 Application(Entity class)이 DB의 정보를 알게되고,

Mapper class가 아닌 Repository를 사용하여 Repository가 어노테이션을 통해 Entity와 DB의 Column을 매핑하게 된다. 그리고 이 과정에 custom queries가 사용될 수 있다.

위와 같은 특징으로 인해 Prisma는 다른 ORM들과 달리 DB와 model 사이에 mismatch가 없고 동기화 문제로 부터 자유로우며 개발자는 SQL이 아닌 데이터와 프로그램에 더 집중할 수 있도록 해준다.




기존 ORM과의 차이


실제 사용 예제를 보면서 model의 선언부터 CRUD 까지 사용법의 차이를 비교해보자. Prisma와 Sequelize, TypeORM을 비교해 보겠다.

Model

// =====================
// Prisma
// =====================
model User {
  id    Int     @id @default(autoincrement())
  name  String?
  email String  @unique
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  authorId  Int?
  author    User?   @relation(fields: [authorId], references: [id])
}


// =====================
// TypeORM
// =====================
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToMany,
  ManyToOne,
} from 'typeorm'

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ nullable: true })
  name: string

  @Column({ unique: true })
  email: string

  @OneToMany((type) => Post, (post) => post.author)
  posts: Post[]
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number

  @Column()
  title: string

  @Column({ nullable: true })
  content: string

  @Column({ default: false })
  published: boolean

  @ManyToOne((type) => User, (user) => user.posts)
  author: User
}


// =====================
// Sequelize
// =====================
module.exports = function(sequelize, DataTypes){
    let user = sequelize.define("User", {
        userID: {
            filed: "user_id",
            type: DataTypes.STRING(50),
            unique: true,
            allowNull: false
        },
        password: {
            field: "password",
            type: DataTypes.STRING(30),
            allowNull: false
        }
    }, {
        underscored: true,
        freezeTableName: true,
        tableName: "user"
    });
    return user;
}

Sequelize에서는 주로 schema를 정의해서 migrate 하기보단 주로 터미널에서 sequelize-cli 를 사용해 테이블을 생성한다.

yarn sequelize model:generate --name 테이블 이름 --attributes 컬럼이름

테이블을 생성할 경우 위의 코드가 자동생성 된다.

List

// =====================
// Prisma
// =====================
const posts = await prisma.post.findMany({
  where: {
    title: { contains: 'Hello World' },
  },
})


// =====================
// TypeORM
// =====================
const posts = await postRepository.find({
  where: {
    title: ILike('%Hello World%'),
  },
})


// =====================
// Sequelize
// =====================
const post = await Post.findAll({
  raw: true,
  where: {
    title: {
      [Op.like]: '%Hello World%',
    },
  },
})

Create

// =====================
// Prisma
// =====================
const newUser = await prisma.user.create({
  data: {
    name: 'Alice',
    email: 'email@email.com',
  },
})


// =====================
// TypeORM
// =====================
const userRepository = getManager().getRepository(User)
userRepository.insert({
  name: 'Alice',
  email: 'email@email.com',
})


// =====================
// Sequelize
// =====================
const user = await User.create({
  name: 'Alice',
  email: 'email@email.com',
})

Read

// =====================
// Prisma
// =====================
const posts = await prisma.user.findUnique({
  where: {
    id: 2,
  },
  include: {
    post: true,
  },
})


// =====================
// TypeORM
// =====================
const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
  relations: ['posts'],
})


// =====================
// Sequelize
// =====================
const user = await User.findByPk(id, {
  include: [
    {
      model: Post,
    },
  ],
})

Update

// =====================
// Prisma
// =====================
const user = await prisma.user.update({
  data: {
    name: 'Alicia',
  },
  where: {
    id: 2,
  },
})


// =====================
// TypeORM
// =====================
const userRepository = getRepository(User)
const updatedUser = await userRepository.update(id, {
  name: 'James',
  email: 'james@prisma.io',
})


// =====================
// Sequelize
// =====================
await User.update({
  name: 'James',
  email: 'james@prisma.io',
})

Delete

// =====================
// Prisma
// =====================
const deletedUser = await prisma.user.delete({
  where: {
    id: 10,
  },
})


// =====================
// TypeORM
// =====================
const userRepository = getRepository(User)
await userRepository.delete(id)


// =====================
// Sequelize
// =====================
await user.destroy()



결론


사용법에서 사실 획기적인 차이는 느껴지지 않는다. Prisma와 기존의 ORM의 가장 큰 차이는 사용법보다는 개념적인 부분과 작동 방식에 있는 듯 하다. 단순히 사용만 해보면 차이를 잘 체감하지 못할 수 있으나 DB와 Application의 연동의 주체가 Application이 되고 매핑된 테이블의 칼럼을 찾아가는 방식이 아닌 데이터의 주권(?)을 Application이 가지는 방식이라는 차이가 설계에 있어서 매우 중요한 요소가 될 것 같고 어떻게 보면 진정한 의미의 ORM이 아닌가 싶기도 하다.

profile
개발공부를해보자

0개의 댓글