명예의 전당 리팩토링하기 (3)

JinYoungMo·2024년 3월 30일

이번에는 데이터 일관성을 위한 트랜잭션 관리로 리팩토링 해보려고 한다

// 명예의 전당 매서드 일주일마다 업데이트
  @Cron(CronExpression.EVERY_WEEK)
  async updateHallOfFame() {
    const lastWeekStart = new Date();
    lastWeekStart.setDate(lastWeekStart.getDate() - 7 - lastWeekStart.getDay())
    lastWeekStart.setHours(0, 0, 0, 0);

    const lastWeekEnd = new Date(lastWeekStart);
    lastWeekEnd.setDate(lastWeekEnd.getDate() + 6)
    lastWeekEnd.setHours(23, 59, 59, 999);

    // 지난주에 진행된 투표 데이터를 조회
    const lastWeekVotes = await this.votesRepository.find({
      where: {
        createdAt: Between(lastWeekStart, lastWeekEnd),
      },
    });

    // 투표 기반으로 명예의 전당 집계
    const hallOfFameData = this.aggVotesForHallOfFame(lastWeekVotes)

    await this.updateHallOfFameDatabase(hallOfFameData);
  }


  // 날짜 추상화 매서드
  private getThisMonthRange(){
    const start = new Date();
    start.setDate(1); // 이번달 첫쨰날
    start.setHours(0, 0, 0, 0) // 자정

    const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
    end.setHours(23, 59, 59, 999) // 하루의 마지막 시간

    return { start, end }
  }
  // 투표 데이터 집계 매서드
  private async aggVotesForHallOfFame(votes: Votes[]){
    const { start, end } = this.getThisMonthRange();

    const candidates = await this.votesRepository
    .createQueryBuilder("vote")
    .select(['vote.id', 'vote.title1', 'vote.title2'])
    .addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
    .where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
    .having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
    .orderBy('totalVotes', "DESC")
    .limit(1000)// 1000개 이상의 데이터가 없어도 남은 데이터 만큼 올라간다. 즉 데이터 집계 상한선이 1000개 라는 뜻
    .groupBy("vote.id")
    .getRawMany();

    return candidates
  }

  // DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.
  private async updateHallOfFameDatabase(hallOfFameData: any){
    // 한번에 저장
    const newHallOfFameEntries = hallOfFameData.map(data => {
      const newHallOfFameEntry = new TrialHallOfFames();
      newHallOfFameEntry.id = data.id // vote table의 id임다
      newHallOfFameEntry.userId = data.trial.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
      newHallOfFameEntry.title = data.title1 + 'Vs' + data.title2
      newHallOfFameEntry.content = data.trial.content // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
      newHallOfFameEntry.createdAt = new Date();
      newHallOfFameEntry.updatedAt = new Date();
      return newHallOfFameEntry;
    });
      // DB에 새로운 명전 저장
      await this.trialHallOfFamesRepository.save(newHallOfFameEntries)

    }
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.
  private async updateHallOfFameDatabase(hallOfFameData: any){
    // 한번에 저장
    const newHallOfFameEntries = hallOfFameData.map(data => {
      const newHallOfFameEntry = new TrialHallOfFames();
      newHallOfFameEntry.id = data.id // vote table의 id임다
      newHallOfFameEntry.userId = data.trial.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
      newHallOfFameEntry.title = data.title1 + 'Vs' + data.title2
      newHallOfFameEntry.content = data.trial.content // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
      newHallOfFameEntry.createdAt = new Date();
      newHallOfFameEntry.updatedAt = new Date();
      return newHallOfFameEntry;
    });
      // DB에 새로운 명전 저장
      await this.trialHallOfFamesRepository.save(newHallOfFameEntries)

    }

에 트랜잭션을 적용해 주었다.

 private async updateHallOfFameDatabase(hallOfFameData: any) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      // 한 번에 저장하는 로직을 트랜잭션 내에서 실행
      const newHallOfFameEntries = hallOfFameData.map(data => /* 데이터 매핑 로직 유지 */);
      await queryRunner.manager.save(newHallOfFameEntries);

      await queryRunner.commitTransaction(); // 업데이트 로직이 성공하면 커밋
    } catch (err) {
      await queryRunner.rollbackTransaction(); // 실패하면 롤백
      throw err; // 에러를 다시 던져 상위 로직에서 처리할 수 있도록 함
    } finally {
      await queryRunner.release(); // 트랜잭션 종료
    }
  }```
profile
blockchain core & payments and stable coins

0개의 댓글