Sequelize Hooks 사용해보기

doldolma·2021년 11월 24일
2

Sequelize

목록 보기
1/1

Hooks

Sequelize에서 Hooks는 라이프 사이클 이벤트 전후에 실행되는 함수이다.
모델을 저장하기 전 또는 저장 후에 미리 지정해둔 작업등을 할 수 있는 것이다.

나는 프로젝트에서 이미지를 CDN서버에 저장하고 경로를 DB에 저장했는데 DB에서 해당 항목이 삭제될 때 CDN에서 같이 사진을 삭제하도록 hooks를 작성해보았다.

Hooks 순서

(1)
  beforeBulkCreate(instances, options)
  beforeBulkDestroy(options)
  beforeBulkUpdate(options)
(2)
  beforeValidate(instance, options)

[... validation happens ...]

(3)
  afterValidate(instance, options)
  validationFailed(instance, options, error)
(4)
  beforeCreate(instance, options)
  beforeDestroy(instance, options)
  beforeUpdate(instance, options)
  beforeSave(instance, options)
  beforeUpsert(values, options)

[... creation/update/destruction happens ...]

(5)
  afterCreate(instance, options)
  afterDestroy(instance, options)
  afterUpdate(instance, options)
  afterSave(instance, options)
  afterUpsert(created, options)
(6)
  afterBulkCreate(instances, options)
  afterBulkDestroy(options)
  afterBulkUpdate(options)

생각보다 다양하다...
생성 전후, 삭제 전후, 업데이트 전후.. 등등

후크 선언은 모델파일에 같이 해주었다.

ex) 모델 예시

User.init(
  {
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false,
    },
    profileImage: {
      type: DataTypes.STRING,
      defaultValue: '',
      get() {
        const image = this.getDataValue('profileImage');
        if (image) return `${process.env.CDN_SERVER}/${image}`;
        return '';
      },
    },
    nickname: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    password: {
      type: DataTypes.STRING,
      set(value) {
        this.setDataValue('password', createHash('sha512')
          .update(value + process.env.SECRET_SALT).digest('base64'));
      },
    },
  },
  {
    sequelize,
    modelName: 'User',
    updatedAt: false,
    hooks: {
      afterDestroy: async (user, options) => {
        const image = user.getDataValue('profileImage');
        if (image) {
          await s3.delete(image);
        }
      });
    },
  },
);

hooks에 대한 정의는 모델에서 컬럼을 정의하고 옵션을 넣을 때 hooks에 넣어주면 된다. 위 코드처럼 hooks에 함수를 등록해두면 삭제된 후 함수에 모델인스턴스가 인자로 들어오고 실행된다.

hooks 이벤트들을 보면 bulk일때와 아닐때를 구분하고 있는데
Model.destroy({조건})으로 삭제할 때와 ModelInstance.destory()로 삭제할 때는 발생되는 이벤트가 다르다. 전자로 삭제할 경우 여러 항목이 삭제될 수 있으므로 bulkDestory 이벤트가 발생한다.
조건으로 삭제하고 afterbulkDestory 이벤트가 아니라 각각의 항목에 afterDestory 이벤트가 발생하게 하고 싶다면 Model.destory({...조건, indivisualHooks: true}) 이것 처럼 indivisualHooks 값을 true로 주면 삭제된 각각의 항목에 대해 개별적으로 이벤트가 발생한다.
나는 모델 인스턴스를 불러온 후 삭제하므로 afterDestory 이벤트에 등록했다.

트랜잭션 이후

하지만 또 다른 문제가 있었다. afterDestory이벤트는 DB에서 해당 항목이 삭제된 이후 실행될 것이라고 기대했지만 DB에 삭제 요청을 보낸 이후 삭제가 올바르게 되지 않았을 때도 hooks가 실행된다는 것이었다.

때문에 트랜잭션이 성공한 이후 실행될 수 있는 방법을 찾아보았다.
공식문서에는 hooks 함수에서 options에 트랜잭션 정보를 받아올 수 있다는 이야기가 있었다. 그리고 추가 구글링을 해본 결과 생성한 트랜잭션에 대해서도 hooks를 넣을 수 있다는 사실을 알게 되었다.

그래서 나는 afterDestory 이벤트가 발생했을 때 트랜잭션이 있으면 해당 트랜잭션에 대해서 afterCommit hooks를 추가해서 커밋이 성공하고 나서 사진을 삭제할 수 있도록 수정했다.

hooks: {
        afterDestroy: (user, options) => {
          if (options.transaction) {
            options.transaction.afterCommit(async () => {
              debug('회원탈퇴 커밋 완료. 이미지 삭제시작');
              const image = user.getDataValue('profileImage');
              if (image) {
                await s3.delete(image);
              }
            });
          }
        },
      },

afterDestory이벤트가 발생하면 트랜잭션이 있는지 검사해서 트랜잭션에 afterCommit 함수를 등록해서 커밋이 성공적으로 이루어졌을 때만 사진을 삭제하도록 구현했다.

profile
코딩은 재밌어

0개의 댓글