[2024.07.10 TIL] 내일배움캠프 60일차 (과제 해설 강의 시청, 개인과제 리팩토링, 공연 생성 코드 구조 변경, 관계를 통한 데이터 저장)

My_Code·2024년 7월 10일
0

TIL

목록 보기
76/112
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.


💻 TIL(Today I Learned)

📌 Today I Done

✏️ 공연 생성 코드 구조 변경

  • 해설 강의 17번에서 공연 생성 기능을 구현하고 있었음

  • 튜터님께서는 각각의 Repository에 접근해서 데이터를 넣는 것이 아니라 공연 엔티티에 정의되어 있는 관계를 통해서 각 Repository에 데이터를 넣는 방법을 사용하셨음

  • 내가 작성한 기존의 코드는 순차적으로 공연 데이터, 시간 데이터, 장소 데이터, 좌석 데이터 와 같이 순차대로 Repository에 접근해서 데이터를 저장했음

  • 튜터님께서 사용하신 방법으로도 도전하고자 공연 생성 코드를 전체적으로 수정함

  • 아래가 기존에 작성한 공연 생성 코드임

  // 공연 등록
  async createShow(createShowDto: CreateShowDto, files: Express.Multer.File[]) {
    const {
      title,
      content,
      category,
      runningTime,
      times,
      placeName,
      seatA,
      seatS,
      seatR,
      seatVip,
      priceA,
      priceS,
      priceR,
      priceVip,
    } = createShowDto;

    // 트랜젝션 연결 설정 초기화
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    // 이미지 업로드 후 반환되는 이미지 URL 배열
    let images: string[];

    try {
      // show 테이블에 데이터 저장
      const show = this.showRepository.create({
        title,
        content,
        category,
        runningTime,
      });
      // 다른 테이블에서 show.id를 사용하기 때문에 따로 저장
      await queryRunner.manager.save(show);

      // 이미지 데이터를 업로드
      images = await this.awsService.uploadImage(files);

      // show_price 테이블에 데이터 저장
      const showPrice = this.showPriceRepository.create({
        showId: show.id,
        priceA,
        priceS,
        priceR,
        priceVip,
      });

      // show_time 테이블에 데이터 저장
      const showTimes = times.map((time) =>
        this.showTimeRepository.create({
          showId: show.id,
          showTime: time,
        }),
      );

      // show_image 테이블에 데이터 저장
      const showImages = images.map((image) =>
        this.showImageRepository.create({
          showId: show.id,
          imageUrl: image,
        }),
      );

      // queryRunner를 병렬로 처리해서 데이터베이스에 저장
      await Promise.all([
        queryRunner.manager.save(showPrice),
        queryRunner.manager.save(showTimes),
        queryRunner.manager.save(showImages),
      ]);

      // show_place 테이블에 데이터 저장
      const totalSeat: number = seatA + (seatS ?? 0) + (seatR ?? 0) + (seatVip ?? 0);
      const showPlace = showTimes.map((showTime) =>
        this.showPlaceRepository.create({
          showId: show.id,
          showTimeId: showTime.id,
          placeName,
          totalSeat,
          seatA,
          seatS,
          seatR,
          seatVip,
        }),
      );
      await queryRunner.manager.save(showPlace);

      // 장소(시간)별, 등급별 좌석 정보 추가하기
      for (let k = 0; k < showPlace.length; k++) {
        let seatNumber = 1;
        // A 좌석 데이터 저장
        for (let i = 1; i <= showPlace[k].seatA; i++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              showPlace[k].showTimeId,
              seatNumber,
              Grade.A,
              showPrice.priceA,
            ),
          );
          seatNumber++;
        }

        // S 좌석 데이터 저장
        for (let i = 1; i <= showPlace[k].seatS; i++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              showPlace[k].showTimeId,
              seatNumber,
              Grade.S,
              showPrice.priceS,
            ),
          );
          seatNumber++;
        }

        // R 좌석 데이터 저장
        for (let i = 1; i <= showPlace[k].seatR; i++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              showPlace[k].showTimeId,
              seatNumber,
              Grade.R,
              showPrice.priceR,
            ),
          );
          seatNumber++;
        }

        // Vip 좌석 데이터 저장
        for (let i = 1; i <= showPlace[k].seatVip; i++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              showPlace[k].showTimeId,
              seatNumber,
              Grade.VIP,
              showPrice.priceVip,
            ),
          );
          seatNumber++;
        }
      }

      // 출력 형식 지정
      const createdShow = {
        id: show.id,
        title,
        content,
        runningTime,
        placeName,
        totalSeat,
        priceA,
        priceS,
        priceR,
        priceVip,
        showTimes: showTimes.map((time) => time.showTime),
        showImages: showImages.map((image) => image.imageUrl),
        createdAt: show.createdAt,
        updatedAt: show.updatedAt,
      };

      await queryRunner.commitTransaction();
      return createdShow;
    } catch (err) {
      await queryRunner.rollbackTransaction();
      // 트랜젝션 실패 시 S3 이미지도 롤백
      await this.awsService.rollbackS3Image(images);
      throw new InternalServerErrorException(SHOW_MESSAGE.CREATE_SHOW.FAIL);
    } finally {
      await queryRunner.release();
    }
  }
  • 위 코드도 정상적으로 데이터가 저장되었기에 과제 제출은 위의 코드로 제출함

  • 아래 코드는 튜터님께서 사용하신 방법을 토대로 간단하게 구조를 변경했음

  // 공연 등록
  async createShow(createShowDto: CreateShowDto) {
    const { showImages, showSchedules, showPrice, showPlace, ...restOfShow } = createShowDto;

    // 트랜젝션 연결 설정 초기화
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    // 총 좌석 수
    const totalSeat: number =
      showPlace.seatA + (showPlace.seatS ?? 0) + (showPlace.seatR ?? 0) + (showPlace.seatVip ?? 0);

    try {
      const show = this.showRepository.create({
        ...restOfShow,
        showImages,
        showPrice,
        showSchedules: showSchedules.map((showSchedule) => {
          return {
            ...showSchedule,
            ...showPlace,
            totalSeat,
          };
        }),
      });
      await queryRunner.manager.save(show);

      for (let i = 0; i < showSchedules.length; i++) {
        let seatNumber = 1;
        // A 좌석 데이터 저장
        for (let j = 1; j <= showPlace.seatA; j++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              show.showSchedules[i].id,
              seatNumber,
              Grade.A,
              showPrice.priceA,
            ),
          );
          seatNumber++;
        }

        // S 좌석 데이터 저장
        for (let j = 1; j <= showPlace.seatS; j++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              show.showSchedules[i].id,
              seatNumber,
              Grade.S,
              showPrice.priceS,
            ),
          );
          seatNumber++;
        }

        // R 좌석 데이터 저장
        for (let j = 1; j <= showPlace.seatR; j++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              show.showSchedules[i].id,
              seatNumber,
              Grade.R,
              showPrice.priceR,
            ),
          );
          seatNumber++;
        }

        // Vip 좌석 데이터 저장
        for (let j = 1; j <= showPlace.seatVip; j++) {
          await queryRunner.manager.save(
            this.seatService.createSeat(
              show.id,
              show.showSchedules[i].id,
              seatNumber,
              Grade.VIP,
              showPrice.priceVip,
            ),
          );
          seatNumber++;
        }
      }

      await queryRunner.commitTransaction();

      return {
        ...restOfShow,
        showImages: show.showImages.map((image) => image.imageUrl),
        showSchedules: show.showSchedules.map((schedule) => {
          return {
            showTime: schedule.showTime,
            seatA: schedule.seatA,
            seatS: schedule.seatS,
            seatR: schedule.seatR,
            seatVip: schedule.seatVip,
          };
        }),
        showPrice: {
          priceA: show.showPrice.priceA,
          priceS: show.showPrice.priceR,
          priceR: show.showPrice.priceR,
          priceVip: show.showPrice.priceVip,
        },
      };
    } catch (err) {
      console.log(err);
      await queryRunner.rollbackTransaction();
      // 트랜젝션 실패 시 S3 이미지도 롤백
      await this.awsService.rollbackS3Image(showImages.map((image) => image.imageUrl));
      throw new InternalServerErrorException(SHOW_MESSAGE.CREATE_SHOW.FAIL);
    } finally {
      await queryRunner.release();
    }
  }
  • 코드의 분량 자체는 크게 변화하지 않았지만 가장 큰 변화는 공연 Repository에만 접근해서 관계를 형성한 Repository에 데이터를 저장하는 방식임

  • 기존에는 각각의 Repository에 접근해서 데이터를 넣었지만 위 코드는 해당 엔티티의 관계를 통해서 데이터를 저장했음

  • 위 코드처럼 구조를 변경한 이유는 그저 이런 저런 방식으로 코드를 작성하고 싶었기 때문임

  • 그리고 위 코드에서 관계를 형성할 때, 필요한 외래키를 명시한적이 없는데 이건 아래와 같이 엔티티에서 { cascade: true } 라는 관계 옵션을 사용했기 때문에 자동으로 외래키가 적용됨

@Entity({
  name: SHOW_CONSTANT.ENTITY.SHOW.NAME,
})
export class Show {
  @PrimaryGeneratedColumn()
  id: number;

  /**
   * 공연 제목
   * @example "공연 제목 테스트"
   */
  @Index()
  @IsString()
  @IsNotEmpty({ message: SHOW_MESSAGE.DTO.TITLE.IS_NOT_EMPTY })
  @Column({ type: 'varchar', nullable: false })
  title: string;

  //...

  @OneToMany(() => ShowImage, (showImage) => showImage.show, { cascade: true })
  showImages: ShowImage[];

  @OneToMany(() => ShowSchedule, (showSchedule) => showSchedule.show, { cascade: true })
  showSchedules: ShowSchedule[];
}


📌 Tomorrow's Goal

✏️ 플러스 주차 팀프로젝트 설계

  • 내일은 플러스 주차 팀프로젝트 발제를 하는 날임

  • 주제는 프로젝트 협업 도구인 트렐로를 만드는 것임

  • 그래도 트렐로가 어떤 툴인지 알기 때문에 기본적인 이해에는 도움이 될 것 같음

  • 발제 첫 날이기 때문에 팀 노션에 기본적인 깃허브 룰, 커밋 룰, 코드 컨벤션을 작성할 예정

  • 그리고 프로젝트에 대한 와이어프레임과 ERB, API 명세서를 작성할 예정

  • 시간만 된다면 깃허브 레포지터리 생성과 기본적인 프로젝트의 틀을 생성하는 것이 목표임



📌 Today's Goal I Done

✔️ 개인과제 해설 강의 시청 및 코드 리팩토링

  • 오늘은 해설 강의를 바탕으로 공연 등록관련 코드를 리팩토링함

  • 사실 공연 등록 메서드의 코드를 대부분 수정했다고 해도 과언이 아님

  • 그 과정에서 1:1 관계로 연결한 공연 장소 엔티티와 시간 엔티티를 하나의 엔티티로 합침

  • 튜터님께서 1:1 관계는 컬럼의 수가 너무 많거나 자주 접근하지 않는 경우를 제외하면 사용하지 않는 것이 좋다고 하셨기에 엔티티부터해서 관계있는 코드들을 수정했음

  • 튜터님 말씀대로 수정하니 1:1 관계일 때 데이터 저장에서 발생하는 문제를 해결했음



📌 ⚠️ 구현 시 발생한 문제

✔️ 공연 생성 시 1:1 관계의 장소 데이터가 저장되지 않음

  • 모든 데이터를 따로따로 저장하던 코드에서 엔티티의 관계를 이용한 방법으로 저장하도록 코드를 리팩토링함

  • 리팩토링을 진행하던 중 공연 생성을 할 때 1:1 관계를 맺고 있는 공연 시간 엔티티와 공연 장소 엔티티에서 시간 데이터는 저장되었지만 장소 데이터는 제대로 저장되지 않음

  • 공연은 여러 개의 시간대를 가지고 있기 때문에 공연과 시간은 1:N관계고, 그 여러 개의 시간은 각각 하나의 장소 데이터를 가지고 있기 때문에 시간과 장소는 1:1관계를 가짐

  • 결국 방법을 찾지 못해서 튜터님께 갔음

  • 튜터님께서 엔티티의 구조부터가 좋지 않은 형태라고 하셨음

  • 1:1 관계일 때 굳이 엔티티를 따로 뺄 필요가 없다고 하셨음

  • 1:1 관계는 정말 접근할 일이 많이 없을 때에나 사용한다고 하셨음

  • 그렇기에 기존에 있던 문제는 시간과 장소 엔티티를 하나의 엔티티로 사용하면 해결되는 문제라고 하셨기에 코드 전체를 새롭게 리팩토링 함

  • 정말 프로젝트의 전체를 고쳐야 하기 떄문에 그 과정을 따로 기록을 하지 못함

profile
조금씩 정리하자!!!

0개의 댓글