written by yechoi

[TypeORM] Active Record, Data Mapper 비교 본문

Born 2 Code/Node Modules

[TypeORM] Active Record, Data Mapper 비교

yechoi 2021. 7. 24. 13:50
반응형

Active Record

모델 안에 모든 쿼리 방식을 정해놓고, 모델 메소드를 활용해 오브젝트를 save-remove-load 하는 방식. 간단히 말하자면 모델 내부에 데이터 베이스에 접근하는 방식을 구현하는 패턴.

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;

}

모든 Active Record 엔티티는 BaseEntity 클래스를 확장해야 한다. 다음은 이러한 엔티티를 사용하는 방법에 대한 예시.

// 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.find({ isActive: true });
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });

BaseEntity 는 스탠다드 Repository 의 메소드 대부분을 가지고 있다. active recorde 엔티티를 다루기 위해서 RepositoryEntityManager 를 사용할 필요가 없다.

성과 이름으로 유저의 정보를 리턴하는 함수가 있다고 하자. 우리는 이를 User 클래스에스 스태틱 메소드로 함수로 만들 수 있다.

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();
    }

다른 메소드처럼 사용하면 된다.

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

 

Data Mapper

Data Mapper 방식에선 쿼리 메소드를 "레포지토리"라고 불리는 각각 별도의 클래스에 정의해야 한다. 그리고 해당 레포지토리를 사용해 객체를 save-remove-load한다. data mapper 방식에서 당신의 엔티티는 멍청하다. 그저 속성을 정할 뿐이고, 몇개의 더미 메소드를 가질 수도 있다.

간단히 말하자면 data mapper는 데이터 베이스를 모델 대신 레포지토리에서 접근하는 방식이다.

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

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;

}

이러한 엔티티를 사용하는 방식이다.

const userRepository = connection.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.find({ isActive: true });
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });

성과 이름으로 user를 반환하는 함수를 만들어보자. 이러한 함수는 "커스텀 레포지토리"에 만들어야 한다.

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 = connection.getCustomRepository(UserRepository);
const timber = await userRepository.findByName("Timber", "Saw");

 

어떤 걸 골라야 할까

결정은 당신의 몫이다. 두가지 방식 모두 나름의 장단이 있다.

소프트웨어 개발에서 우리가 명심해야 할 한가지는 어떻게 우리의 어플리케이션을 유지보수할 것인지다. Data Mapper 방식은 유지보수성이 좋으며 큰 앱에서 더 효과적이다. Active Record 는 작은 앱에서 간단하게 만드는 데 적합하다. 그리고 간단함은 언제나 더 좋은 유지보수성을 만드는 열쇄다.

🔗 Active Recore vs Data Mapper (en/docs)

반응형