카테고리 없음

nestjs - abstract class (추상 클래스) 란?

STUFIT 2025. 2. 2. 14:33
반응형

nestjs에서 enttiy를 작성할 때, 공통적으로 들어가는 컬럼들을 abstarct entity로 빼서 extends 를 해서 사용하는데, 그러면 abstarct class란 대체 뭘까?

 

🔥 abstract class BaseEntity의 역할

✅ abstract 키워드는 추상 클래스를 정의할 때 사용됨.
BaseEntity를 직접 인스턴스화할 수 없고, 다른 엔티티가 extends 해서만 사용할 수 있음.
✅ typeorm에서 공통 필드를 관리하는 베이스 엔티티를 만들 때 자주 사용됨.


✅ 1. abstract class란?

📌 추상 클래스는 직접 인스턴스화할 수 없고, 자식 클래스에서 상속받아 사용해야 함.

abstract class Animal {
  abstract makeSound(): void; // ✅ 추상 메서드 (자식 클래스에서 구현해야 함)
  move(): void {
    console.log('Moving...');
  }
}

class Dog extends Animal {
  makeSound() {
    console.log('Bark! 🐶');
  }
}

const dog = new Dog();
dog.makeSound(); // ✅ "Bark! 🐶"
dog.move();      // ✅ "Moving..."

추상 클래스는 abstract 메서드를 가질 수 있으며, 자식 클래스에서 반드시 구현해야 함.
✅ Dog 클래스는 Animal을 상속했기 때문에 makeSound()를 구현해야 함.


✅ 2. BaseEntity에서 abstract의 역할

📌 NestJS + TypeORM에서 BaseEntity를 abstract로 선언하면:

  • 공통 필드 (createdAt, updatedAt, useYn)를 모든 엔티티에서 자동으로 상속
  • 직접 인스턴스화할 수 없음 (new BaseEntity() 불가)
  • 자식 엔티티(UserEntity, OrderEntity 등)에서 extends BaseEntity로 상속받아 사용
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from './base.entity';

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

  @Column()
  name: string;
}

이제 UserEntity는 BaseEntity의 필드(createdAt, updatedAt, useYn)를 자동으로 상속!
✅ UserEntity 테이블을 생성하면 created_at, updated_at, use_yn 컬럼이 자동 포함됨.


✅ 3. abstract를 쓰지 않으면?

📌 abstract를 제거하면 BaseEntity를 직접 인스턴스화할 수 있음.

const base = new BaseEntity(); // ❌ 이런 식으로 사용 가능해짐 (원치 않는 동작)

우리는 BaseEntity를 직접 사용할 목적이 아니라 상속용으로만 쓰고 싶기 때문에 abstract를 사용!


✅ 4. abstract class vs interface 차이점

  abstact class interface
상속 방식 extends 사용 implements 사용
필드(변수) 포함 가능? ✅ 가능 ❌ 불가능 (구현해야 함)
메서드 구현 가능? ✅ 가능 ❌ 불가능 (구현 필요)
인스턴스 생성 가능? ❌ 불가능 🚫 개념 없음
예제 abstract class BaseEntity {} interface IUser {}

공통 컬럼을 가진 엔티티라면 abstract class를 사용하는 것이 좋음!
✅ interface는 typeorm에서 엔티티 정의에 사용할 수 없으므로 abstract class가 적절한 선택.


 

🔥 NestJS에서 abstract class를 사용하는 경우

NestJS에서도 abstract class를 사용하지만, Spring처럼 자주 사용되지는 않음.
TypeScript는 다중 상속을 지원하지 않기 때문에, 인터페이스(interface)와 조합해서 사용하는 경우가 많음.
하지만 공통 로직이 많거나 Entity, Service, Controller에서 재사용할 부분이 있다면 abstract class가 유용함!


✅ 1. abstract class를 가장 많이 사용하는 경우

📌 NestJS에서 abstract를 주로 사용하는 곳은 다음과 같음.

 사용위치 설명
1️⃣ BaseEntity 공통 엔티티 필드 (createdAt, updatedAt) 상속
2️⃣ AbstractService CRUD 로직을 공통적으로 관리할 때
3️⃣ AbstractController 공통 API 엔드포인트를 묶을 때
4️⃣ Repository 패턴 TypeORM + @InjectRepository를 사용하는 경우

아래에서 실제 예제와 데이터를 보면서 하나씩 확인해 보자!


✅ 2. abstract class BaseEntity (공통 엔티티)

📌 Spring에서도 공통 Entity를 @MappedSuperclass로 빼듯이, NestJS에서도 BaseEntity로 만듦.

import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Column } from 'typeorm';

export abstract class BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp', comment: '생성일' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp', comment: '수정일' })
  updatedAt: Date;

  @Column({ name: 'use_yn', type: 'char', length: 1, default: 'Y', comment: '사용 여부' })
  useYn: string;
}

사용 예시 (UserEntity, OrderEntity에서 BaseEntity 상속)

import { Entity, Column } from 'typeorm';
import { BaseEntity } from './base.entity';

@Entity()
export class UserEntity extends BaseEntity {
  @Column()
  name: string;

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

💡 Spring에서 @MappedSuperclass와 같은 역할!
💡 모든 엔티티에서 createdAt, updatedAt, useYn을 자동으로 상속받음.


✅ 3. abstract class AbstractService (공통 서비스)

📌 Spring의 @Service에서 공통 CRUD 메서드를 AbstractService로 만들듯이, NestJS에서도 가능!

import { Repository, DeepPartial, FindOneOptions, FindManyOptions } from 'typeorm';

export abstract class AbstractService<T> {
  constructor(protected readonly repository: Repository<T>) {}

  async create(data: DeepPartial<T>): Promise<T> {
    return this.repository.save(data);
  }

  async findAll(options?: FindManyOptions<T>): Promise<T[]> {
    return this.repository.find(options);
  }

  async findOne(options: FindOneOptions<T>): Promise<T | null> {
    return this.repository.findOne(options);
  }

  async update(id: number, data: Partial<T>): Promise<void> {
    await this.repository.update(id, data);
  }

  async delete(id: number): Promise<void> {
    await this.repository.delete(id);
  }
}

사용 예시 (UserService에서 AbstractService 상속)

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AbstractService } from './abstract.service';
import { UserEntity } from '../entities/user.entity';

@Injectable()
export class UserService extends AbstractService<UserEntity> {
  constructor(@InjectRepository(UserEntity) repository: Repository<UserEntity>) {
    super(repository);
  }
}

💡 Spring에서 BaseService를 만들어 재사용하는 것과 유사!
💡 CRUD 로직을 AbstractService로 한 번만 만들고, UserService, OrderService 등에서 상속받아 사용 가능!


✅ 4. abstract class AbstractController (공통 컨트롤러)

📌 Spring에서는 @RestController를 공통으로 만들 수 있는데, NestJS에서도 비슷한 방식이 가능!

import { Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { AbstractService } from './abstract.service';

export abstract class AbstractController<T> {
  constructor(protected readonly service: AbstractService<T>) {}

  @Post()
  async create(@Body() data: T): Promise<T> {
    return this.service.create(data);
  }

  @Get(':id')
  async findOne(@Param('id') id: number): Promise<T | null> {
    return this.service.findOne({ where: { id } });
  }

  @Get()
  async findAll(): Promise<T[]> {
    return this.service.findAll();
  }

  @Put(':id')
  async update(@Param('id') id: number, @Body() data: Partial<T>): Promise<void> {
    return this.service.update(id, data);
  }

  @Delete(':id')
  async delete(@Param('id') id: number): Promise<void> {
    return this.service.delete(id);
  }
}

사용 예시 (UserController에서 AbstractController 상속)

 
import { Controller } from '@nestjs/common';
import { AbstractController } from './abstract.controller';
import { UserEntity } from '../entities/user.entity';
import { UserService } from '../services/user.service';

@Controller('users')
export class UserController extends AbstractController<UserEntity> {
  constructor(protected readonly userService: UserService) {
    super(userService);
  }
}

💡 Spring에서 BaseController를 만들어 재사용하는 것과 동일한 패턴!
💡 UserController, OrderController 등에서 중복된 API 엔드포인트 코드를 최소화할 수 있음.


🔥 최종 결론: NestJS에서 abstract class를 사용하는 경우

적용위치 설명
✅ BaseEntity 공통 엔티티 필드 (createdAt, updatedAt, useYn) 관리
✅ AbstractService CRUD 로직을 한 번만 만들고 재사용
✅ AbstractController API 엔드포인트를 공통으로 관리
✅ Repository 패턴 TypeORM + @InjectRepository에서 활용

💡 Spring의 @MappedSuperclass, BaseService, BaseController와 유사한 패턴을 NestJS에서도 abstract class로 구현 가능!
🚀 abstract를 활용하면 코드 재사용성이 증가하고 유지보수가 쉬워짐! 🔥

 
 
반응형