NestJS에서 TypeORM을 사용하다 보면 Active Record와 Data Mapper라는 말을 종종 들었고 이 둘을 비교하는 글도 볼 수 있었다. 이들은 쿼리 메서드의 정의 방법을 결정하는 패턴인 듯 보인다. 평소에 당연하다는 듯이 Data Mapper 방식을 사용했고 Active Record 패턴은 생소했기에 적어두려고 한다.
TypeORM의 패턴
Node.js에서 많이 사용되는 ORM인 Sequelize는 Active Record Pattern이라고 한다.
그러나 같은 Node.js의 ORM인 TypeORM은 Active Record Pattern뿐만 아니라 Data Mapper 패턴을 지원한다고 한다.
Data Mapper Pattern은 큰 서비스에서 유지 보수하며 개발하기 좋다는 장점이 있고,
Active Record 는 간단하기에 작은 서비스에서 유지 보수하면서 사용하기 좋다.
Active Record Pattern
Active Record Pattern은 Active Record 엔티티가 BaseEntity 클래스를 확장하는 형태이며 모델 안에 모든 쿼리 방식을 정해놓고, 모델 메서드를 활용해 오브젝트를 CRUD 하는 방식이다.
다시 말해 모델 내부에 DataBase에 접근하는 방식을 구현하는 패턴이다.
// user.entity.ts
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();
}
아 BaseEntity는 보통 Repository의 대부분의 메서드의 수행이 가능하다. Active Record 메서드를 다루기 위해서는 Repository나 EntityManager를 사용할 필요가 없다.
// user.service.ts
const timber = await User.findByName("Timber", "Saw");
일반적인 메서드와 같은 방식으로 사용이 가능하다.
Data Mapper Pattern
Data Mapper Pattern은 쿼리 메서드를 Repository라고 하는 모델과 독립적인 클래스에서 정의한다. 그리고 해당 Repository를 경유해 객체를 CRUD 한다. 다시 말하자면 DataBase를 Model이 아닌 Repository에서 접근하는 방식이다.
이런 구조를 가지게 된다면 Data Mapper Pattern에서 정의된 모델 혹은 엔티티는 그저 프로퍼티를 정하는데 그친다.
// user.entity.ts
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
isActive: boolean;
}
Active Record Pattern과 다르게 모델은 위 코드와 같이 단순히 프로퍼티만 정의한다.
쿼리 메서드를 custom repository에서 정의를 해준다.
// user.repository.ts
@CustomRepository(UserEntity)
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();
}
}
위와 같이 정의된 메서드는 serivce에서 repository class를 DI해서 사용하면 된다.
마무리
Active Record Pattern은 모델 내부에 쿼리 메서드를 정의하는 형태로 단순한 CRUD 작업이 많거나 규모가 작은 프로젝트에 빠르게 적용할 수 있는 장점이 있다. 하지만 복잡한 작업을 위한 쿼리를 유지 보수하거나 규모가 큰 프로젝트에서는 적합하지 않은 패턴이 될 수 있다.
Data Mapper Pattern은 Repository를 통해 모델과 DB의 의존성을 떨어트려 구조적인 안정성이 생긴다. 그러기에 Active Record Pattern가 반대로 큰 프로젝트에 적합해 보이며 유지보수도 상대적으로 용의 해 보인다.
현재 내가 주로 사용하는 NestJS의 공식문서에는 Data Mapper Pattern의 방법을 다뤄준다. 이유를 생각해보니 Repository를 DI 하여 사용하는 방법은 IoC 패턴을 사용하는 프레임워크로서 더 Nest 스러운 것이 Data Mapper Pattern이라 그러지 않을까 싶다.
https://typeorm.io/active-record-data-mapper
https://docs.nestjs.com/recipes/sql-typeorm
https://docs.nestjs.com/techniques/database#models
'Backend > Node.js' 카테고리의 다른 글
[FP] fp-ts 란 (0) | 2023.02.07 |
---|---|
[Node.js] npm install 옵션 (0) | 2022.11.16 |
[WEB] Polling과 WebSocket (... socket.io) (0) | 2022.08.20 |
[Express] Joi를 이용해 Validation 검증 (0) | 2022.07.25 |
[Express] swagger를 router에서 분리 정리 (0) | 2022.07.22 |