Database/TypeORM

TypeORM의 데이터 접근 패턴 - Data Mapper, Active Record

wam 2024. 7. 29. 23:28

 

Data Mapper

데이터 매퍼(Data Mapper) 패턴은 데이터베이스와 애플리케이션 객체 사이의 매핑을 분리하여, 데이터베이스 작업을 전담하는 별도의 레포지토리(Repository)나 매퍼 클래스를 사용한다. 이 패턴에서는 엔티티 클래스는 데이터베이스 작업을 직접 수행하지 않고, 매퍼가 데이터베이스와의 상호작용을 담당한다.

 

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

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

  @Column()
  name: string;

  @Column()
  age: number;
}

// Repository를 사용하여 데이터베이스 작업 수행
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  // 추가적인 데이터베이스 작업 메서드를 정의할 수 있습니다.
}
// 추가적인 데이터베이스 작업 메서드를 정의할 수 있습니다.

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

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  // 사용자 이름으로 사용자 찾기
  async findByName(name: string): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.name = :name', { name })
      .getMany();
  }

  // 특정 나이 이상인 사용자 찾기
  async findByMinAge(age: number): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.age >= :age', { age })
      .getMany();
  }

  // 사용자 나이 업데이트
  async updateUserAge(userId: number, newAge: number): Promise<void> {
    await this.createQueryBuilder()
      .update(User)
      .set({ age: newAge })
      .where('id = :id', { id: userId })
      .execute();
  }

  // 사용자 삭제 (Soft Delete 사용)
  async softDeleteUser(userId: number): Promise<void> {
    await this.updateUserAge(userId, null); // age를 null로 설정하거나 다른 Soft Delete 전략을 사용할 수 있습니다.
  }
}
  • Repository라는 별도의 클래스에서 모든 쿼리 방법을 정의하고, 리포지토리를 사용하여 개체를 저장, 제거 및 로드할 수 있다.
  • Data Mapper에서 entity는 매우 멍청하다. 속성을 정의할 뿐이고 일부 "더미 같은" 방법을 가지고 있을 수 있다.
  • 모델 대신 저장소 내에서 데이터베이스에 액세스 할 수 있는 접근 방식이다

 

 

Active Record

액티브 레코드(Active Record) 패턴은 엔티티 클래스가 데이터베이스 작업을 직접 수행하는 방식이다. 엔티티 클래스는 데이터베이스의 레코드와 직접적으로 연관되며, CRUD 작업을 직접 수행할 수 있는 메서드를 포함한다.

 

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';

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

  @Column()
  name: string;

  @Column()
  age: number;

  // CRUD 작업 메서드 (예: find, save, remove) BaseEntity에서 제공됨
}

// 데이터베이스 작업 수행
const user = new User();
user.name = 'John Doe';
user.age = 30;
await user.save(); // 데이터베이스에 저장
// CRUD 작업 메서드 (예: find, save, remove) BaseEntity에서 제공됨


// 사용자 생성
const newUser = User.create({
  name: 'John Doe',
  age: 30
});
await newUser.save(); // save 메서드를 사용하여 데이터베이스에 저장

// 사용자 조회
const user = await User.findOne({ where: { name: 'John Doe' } }); // findOne 메서드를 사용하여 특정 조건의 사용자 조회

// 사용자 목록 조회
const users = await User.find(); // find 메서드를 사용하여 모든 사용자 목록 조회

// 사용자 업데이트
await User.update({ name: 'John Doe' }, { age: 31 }); // update 메서드를 사용하여 특정 조건의 사용자 정보 수정

// 사용자 삭제
await User.delete({ name: 'John Doe' }); // delete 메서드를 사용하여 특정 조건의 사용자 삭제

// 소프트 삭제 (삭제 플래그를 사용)
await User.softRemove({ id: 1 }); // softRemove 메서드를 사용하여 사용자 소프트 삭제
  • Ruby on Rails, Django는 Active Record를 사용한다.
  • 모델 자체 내에서 모든 쿼리 방법을 정의하고 모델 방법을 사용하여 개체를 저장, 제거 및 로드할 수 있다.
  • 모델 내에서 데이터베이스에 액세스할 수 있는 접근 방식이다.

 

 

어떤 것을 선택해야 할까?

 

Active Record

단순함을 유지하는데 도움,소규모 앱에서 유용

  • 데이터 접근 로직과 비즈니스 로직이 결합된 상태.
  • 간단하고 직관적이다.

 

 

Data Mapper

유지 보수성에 도움이 됨, 대규모 앱에서 유용

  • 데이터 접근 로직과 비즈니스 로직이 분리된 형태.
  • 유연하고, 복잡한 도메인 로직을 가진 대규모 어플리케이션에 적합

 

 

NestJS에서는 Data Mapper 패턴을 사용하는 장점

  • TypeORM 개발 환경에서 Repository를 사용하는 모듈을 쓸 수 있기 때문
  • Repository를 사용하면 어디서든지 접근 가능하다.
  • 구현하는 서비스에서 접근이 가능하고 테스팅할 때도 접근이 가능하다.
  • NestJS의 경우에는 자동으로 Repository를 사용할 수 있도록 클래스에 자동으로 준비해 준다.