데이터베이스 이미지 관련 필드 개선기

제이슨·2024년 7월 13일
1
post-thumbnail

배경

방명록 서비스를 만들던 중,

  • 이미지 생성 외의 작업을 하기 위해 s3의 key값을 따로 관리할 필요
  • 프론트에서 렌더링 최적화를 위해 이미지를 사이즈 별로 제공할 필요

가 생겼다.

주석과 함께 기존 코드를 확인해보자.

@Injectable()
export class S3Service {
  ...
  async uploadFile(
      file: Express.Multer.File | undefined,
    ): Promise<string | undefined> {
      ...

      await this.s3.send(uploadCommand);

      // 해당 모듈에서는 파일을 업로드하면, url이 반환된다.
      // 현재 uploadFile 메서드의 영향을 받는 테이블은 두 개이다.
      // GuestBook 테이블과 UserProfile 테이블.
      return `https://${this.bucketName}.s3.${awsRegion}.amazonaws.com/${key}`;
    }
  ...
}
model GuestBook {
  ...
  imageUrl    String? @unique
  ...
}

model UserProfile {
  ...
  avatarImageUrl String? @unique
  homeImageUrl    String? @unique
  ...
}

생각

현재 테이블 상태처럼 URL로 이미지를 관리하면

위의 필요성 두 가지(이미지 생성 외의 작업 처리, 클라이언트 측에서의 빠른 이미지 로드)를 달성할 수 없다.

달성하려고 해도, 서비스 로직에서 매번 Url에서 뒤에 붙어있는 키 값만 따로 떼어내는 작업이 필요하기 때문에 URL이 아닌 key 값을 따로 테이블로 빼서 관리할 필요가 있다.

이렇게 하면, 이미지를 사용하는 곳(클라이언트 측, S3서비스 모듈)에서는 URL만 수정하면(클라이언트 측에서 ${BaseURL}/w140/${key} 이런 식으로 사용한다든지..) 목표를 달성할 수 있다.

방법 1. 이미지 테이블 따로 관리

...

model Image {
  id       String @id
  imageUrl String

  guestBook     GuestBook?   @relation("GuestBookImage")
  avatarProfile UserProfile? @relation("AvatarImage")
  homeProfile   UserProfile? @relation("HomeImage")

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  deleted   Boolean  @default(false)
}

model UserProfile {
  ...

  avatarImageKey String? @unique
  avatarImage    Image?  @relation("AvatarImage", fields: [avatarImageKey], references: [id])

  homeImageKey String? @unique
  homeImage    Image?  @relation("HomeImage", fields: [homeImageKey], references: [id])

  ...
}

model GuestBook {
  ...
  
  imageKey String? @unique
  image    Image?  @relation("GuestBookImage", fields: [imageKey], references: [id])

  ...
}

방법 1은 이미지 테이블을 따로 관리하는 방안이다.

GuestBook과 Image를 1:1, UserProfile과 Image를 1:1 관계로 처리하였으며, UserProfile에 들어가는 이미지는 homeImage와 avatarImage이므로 관계를 명확히 구분하기 위해 relation 어노테이션에 name 값을 주었다.

더하여, 이미지 테이블 그 자체로 기능하는 경우보다 GuestBook과 UserProfile 테이블에서 이미지 테이블을 조인해서 사용하는 경우가 더 많을 것이므로 연관관계의 주인(외래키를 갖는 테이블)을 GuestBook과 UserProfile로 설정하였다.

이렇게 했을 때 생각해 본 몇 가지 장점과 단점은 아래와 같다.

  • 장점
    • 이미지와 관련된 추가 정보(예: 크기, 형식, 업로드 날짜 등)를 쉽게 추가할 수 있다.
    • Image 테이블을 통해 모든 이미지를 한 번에 조회하거나 필터링하기 쉽다.
    • 추후 다른 테이블에서 이미지 관련 데이터를 처리하더라도 관리하기 쉽다(이미지 테이블 내에서 통합해서 관리하므로).
  • 단점
    • 테이블 관계가 이전보다 복잡해진다.
    • 서비스 계층에서 데이터 처리 로직 수정이 방법 2보다 많이 일어난다.

방법 2. 필드명 수정 (~Url -> ~Key)

model GuestBook {
  ...
  imageKey    String? @unique
  ...
}

model UserProfile {
  ...
  avatarImageKey String? @unique
  homeImageKey    String? @unique
  ...
}

방법 2는 단순히 이미지를 사용하는 테이블의 필드명을 ~Url에서 ~Key로 바꾸는 방법이다.

  • 장점
    • 테이블 관계를 여전히 단순하게 유지할 수 있다.
    • 서비스 계층에서 데이터 처리 로직 수정이 정말 단순하다!(url을 key로 바꾸고, key 값만 저장하게 만들면 땡!)
  • 단점
    • 이미지의 중앙화된 관리가 어렵다.
    • 이미지 관련 메타 데이터를 알기 힘들다.

선택

방법 1과 2를 놓고 어떤 것을 선택할지 결정하는 일은 기준이 명확했기 때문에 생각보다 쉬웠다.

확장성과 중앙화된 관리를 원한다면, 방법 1을 선택하면 된다.

반대로 추후 이미지 관련 필드가 추가될 가능성이 적고, 더 단순한 테이블 구조를 선호한다면 방법 2를 선택하면 된다.

현재 프로젝트의 규모가 앞으로 더 확장될 여지가 크지 않고, 이미지 관련 메타 데이터는 S3 Client를 사용해 가져올 수 있으므로 나는 방법 2를 사용해 문제를 해결하였다.

profile
계속 읽고 싶은 글을 쓰고 싶어요 ☺

0개의 댓글