엔터티 리스너와 구독자

ClassBinu·2024년 5월 21일

F-lab

목록 보기
33/65

엔터티 리스너

특정 엔터티 이벤트를 리슨할 수 있음.
근데 데이터베이스에서 리스너를 호출하면 안 됨. 대신 구독자를 사용하기.

엔터티 리스너는 엔터티 자체에 정의되며, 해당 엔터티의 생명주기 이벤트(예: 저장, 로드, 업데이트, 삭제 등)에 반응합니다. 이 리스너들은 각 엔터티 클래스 내에 메소드로 정의되고, 특정 이벤트가 발생할 때 자동으로 호출됩니다.

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

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

    @Column()
    email: string;

    @Column()
    username: string;

    @BeforeInsert()
    async checkUsername() {
        // User 엔터티의 레포지토리를 가져옵니다.
        // 이런 식으로 리스너 안에서 디비 호출하지 말라는 말
        const userRepository = getRepository(User);

        // 같은 username을 가진 사용자가 이미 있는지 확인합니다.
        const userExists = await userRepository.findOne({
            where: { username: this.username }
        });

        if (userExists) {
            throw new Error("Username already exists!");
        }
    }
}

이는 리스너 내에서 추가적인 데이터베이스 I/O를 발생시킵니다. 이는 성능 저하를 초래하고, 트랜잭션의 복잡성을 증가시킬 수 있습니다. 또한, 이러한 방식은 리스너의 본래 목적인 간단한 데이터 검증 및 조건 확인을 넘어서는 로직을 포함하고 있어 권장되지 않습니다.

리스너 예시

@Entity()
export class Post {
    @AfterLoad()
    updateCounters() {
        if (this.likesCount === undefined) this.likesCount = 0
    }
}

@Entity()
export class Post {
    @BeforeInsert()
    updateDates() {
        this.createdDate = new Date()
    }
}

@Entity()
export class Post {
    @AfterInsert()
    resetCounters() {
        this.counters = 0
    }
}

@Entity()
export class Post {
    @BeforeUpdate()
    updateDates() {
        this.updatedDate = new Date()
    }
}

@Entity()
export class Post {
    @AfterUpdate()
    updateCounters() {
        this.counter = 0
    }
}

@Entity()
export class Post {
    @BeforeRemove()
    updateStatus() {
        this.status = "removed"
    }
}

@Entity()
export class Post {
    @AfterRemove()
    updateStatus() {
        this.status = "removed"
    }
}

구독자

@EventSubscriber()
export class PostSubscriber implements EntitySubscriberInterface<Post> {
    listenTo() {
        return Post
    }

    beforeInsert(event: InsertEvent<Post>) {
        console.log(`BEFORE POST INSERTED: `, event.entity)
    }
}

특정 엔터티가 아니라 범용적으로 이벤트를 구독하려면 EntitySubscriberInterface에서 제네릭을 빼면 됨

@EventSubscriber()
export class PostSubscriber implements EntitySubscriberInterface {
    /**
     * Called after entity is loaded.
     */
    afterLoad(entity: any) {
        console.log(`AFTER ENTITY LOADED: `, entity)
    }
}

이벤트 객체

구독자를 사용해서 엔터티 이벤트트를 처리할 때, 각 이벤트 처리 메소드에 이벤트 객체가 전달됨.
이벤트 객체는 다음 객체(인스턴스)가 포함됨.

  • dataSource
  • queryRunner
  • EntityManager

전역 인스턴스가 아니라 이렇게 전달된 인스턴스를 사용해야 한다!

0개의 댓글