본문 바로가기
Programming/TypeScript

[ TypeORM ]Active Record vs Data Mapper

by 코뮤(commu) 2022. 9. 19.
728x90
반응형

https://github.com/typeorm/typeorm/blob/master/docs/active-record-data-mapper.md

 

GitHub - typeorm/typeorm: ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL

ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Elect...

github.com

 

위 글을 읽으면서 정리한 글이다.

 

 

Flask 에서 SQLAlchemy 를 사용하면서 Data Mapper 패턴에 대해서는 잘 알고있다고 생각했는데,

막상 새로운 ORM 앞에서 주저앉는 나... 를 발견했다.

 

각설하고, 바로 본론으로 들어간다.

 

 

What is the Active Record pattern?

 

typeorm은 Active Record 패턴과 Data Mapper 패턴 모두 지원하는데,

Active Record 를 이용하면 모델 자체에 쿼리 메소드를 정의하고, 해당 메소드를 이용하여 객체를 저장/읽기/수정/삭제 한다.

 

간단히 정리하면 이 패턴은 모델 안에서 데이터베이스에 액세스하는 접근 방식인거다.

아래는 공식 문서에서 제공하고 있는 예시다.

 

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from "typeorm"

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

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean

    static findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany()
    }
}

 

해당 메소드 호출은 아래와 같이 진행한다.

 

// example how to save AR entity
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await user.save()

// example how to remove AR entity
await user.remove()

// example how to load AR entities
const users = await User.find({ skip: 2, take: 5 })
const newUsers = await User.findBy({ isActive: true })
const timber = await User.findOneBy({ firstName: "Timber", lastName: "Saw" })

 

 

위 예제 코드에서 생성한 정적메소드 findByName 은 아래와 같이 사용하라고 친절하게 알려준다.

 

const timber = await User.findByName("Timber", "Saw")

 

 

What is the Data Mapper pattern?

 

Data Mapper 방식을 사용하면 분리된 클래스에 쿼리 메소드를 정의한다.

NestJS 를 공부해봤다면 들어봤을 Repository 개념이 여기서 활용된다.

 

이 Repository 를 이용하여 객체를 저장/읽기/수정/삭제 한다.

 

Data Mapper 에서 내 모델, 즉 엔티티는 'dumb' 하다고 알려준다.

엔티티는 속성을 정의하는 역할만 하고 있고, 실제 기능은 repository 가 수행하는 것이다.

 

 

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

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

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}

 

Entity 는 위와 같이 심플하게 정의된다.

Repository 는 아래와 같이 생성하는데,

여기서 함정은 typeorm 버전 0.3.x 버전부터 EntityRepository 데코레이터는 deprecated 되었다는 점이다..^^

그래서 나는 이걸 해결하기 위해서 customRepo를 생성했어야했다.

ㅋㅋ 삽질 재밌다 즐거워요

 

아무튼 아래는 공식문서 예제를 가져온 것이다.

만약 customRepository 를 새로 정의해서 사용한다면

EntityRepository 데코레이션 대신 CustomRepository 를 쓰면 되겠다.

 

import {EntityRepository, Repository} from "typeorm";
import {User} from "../entity/User";

@EntityRepository()
export class UserRepository extends Repository<User> {

    findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany();
    }

}

 

const userRepository = dataSource.getRepository(User)

// example how to save DM entity
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await userRepository.save(user)

// example how to remove DM entity
await userRepository.remove(user)

// example how to load DM entities
const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: "Timber",
    lastName: "Saw",
})

 

 

dataSource 로부터 repository를 가져와 해당 repo에서 모든 동작을 수행하는 것을 확인할 수 있다.

 

 

 

 

무엇을 선택하면 좋을까?

 

규모가 작고 간단한 애플리케이션 -> Active Record

규모가 크고 유지보수할 때 좋아야하는 애플리케이션 -> Data Mapper

728x90
반응형