블로그로 돌아가기

공통 Category 모델화

Marco-2025-11-25 12:16:55

이번 설계는 여러 서비스에서 공통으로 사용하는 카테고리 기능을 하나의 Category 모델과 Polymorphic 기반 CategoryRelation 테이블로 통합해 개발 생산성과 확장성을 높이는 것이 목표입니다. 모델마다 별도 테이블을 만들 필요 없이 어떤 도메인에도 유연하게 카테고리를 연결할 수 있도록 설계한 것이 핵심입니다.

서론

  • 우리 팀은 여러 서비스를 하나의 백엔드 서버와 하나의 DB에서 모두 관리하고 있습니다.
  • 그러다보니 Category 모델의 개념의 분류화, 유형화 등을 쓸일이 많습니다.
  • 이제 그부분을 두가지 방법으로 관리할수 있을것입니다.
    • 단순 String 컬럼을 추가한다.
    • 각각의 모델에 따라 Cateogry 모델을 만들어서 관리한다.
  • 이러다보니, 현재 팀의 모토인 빠르게 서비스를 개발하고 사용자에게 가치를 주는 일을 주기 위해서 개발의 시간이 많이 드는것을 느꼈습니다.
  • 따라서 이번에 시간이 조금 들여도 공통 모델을 만들어서 관리할수 있도록 하는것을 목표로 하였습니다.

해결하고자 하는 방안

  1. Category의 개념이 필요할때마다 Cateogry를 의미하는 모델을 새롭게 추가할 필요가 없어야 한다.
  2. 서비스와 타입에 따라 카테고리가 관리되어야 한다.
  3. 최대한 Category의 공통화를 적용해야 한다.

Cateogry 모델 설계

  • 그래서 일단 Category라는 모델을 만드는것을 목표로 했습니다.

  • 이제부터는 AI와 작업하기 위한 문서를 작성해봅니다.

  • 모델링

    class Cateogry {
      id: Long // PK
      values: Map<String, String> // 다국어화를 위해 처리합니다. ex) {ko: 운동종목, en: excerciseList}
      service: String // weggle-plus 서비스를 의미합니다.
      key: string // 해당 Category의 분류값
      createdAt: DateTime
      updatedAt: DateTiem
    }
    
  • API

    • 프로덕션 레벨
      • 목록 조회
        • key, service를 이용하여 필터링이 가능해야 합니다.
    • 어드민 레벨
      • 목록조회
      • 단건조회
      • 생성
      • 수정
      • 삭제
  • 아주 간단하게 위의 설계를 통한 프롬프트를 전달합니다.

  • 그 이후에 제가 원하는 방식의 테이블, 모델, API들이 모두 생성되었습니다.

  • 이 설계의 단점이 있습니다. key에 대해 타입 안정성이 떨어진다는 점입니다.

    • 이것또한 enum으로 하면 최대한 지킬수 있겠지만, 그렇게 되면 확장성이 떨어지기 떄문에 문자열로 일단 처리합니다.

여러 모델과의 관계를 위한 설계

  • 이제 이걸 모델과 연결하기 위해서는 중간 테이블이 필요합니다.
  • 이부분에 대해서는 생산성과 관련되어 있습니다. 따라서 고려한 대안은 아래와 같습니다.
    • 모든 중간 테이블을 다 만듭니다.
      • 예를들어 A모델, B모델이라면 ACategory, BCategory를 모두 만듭니다.
        • 이거는 생산성을 많이 해결하지 못합니다. 이유는 일단 저는 추가되는 모델에서 Category의 기능을 만들때, 최소한의 생산비용을 원합니다. 모델을 새로 만드는것 자체는 그 비용을 줄이지 못한다고 생각합니다.
    • Super Entity 상속구조
      • 위에 방식과 큰 차이는 BaseEntity를 만들어서 조금이나마 코드를 적게 적는것입니다.
      • 그러나 이것또한 많은 생산성을 해결해주지 못합니다.
    • 하나의 테이블에서 Polymorphic을 통한 해결
      • 예를들어 tagertType, tagetId를 두고 하나의 테이블에서 여러 모델을 관리하는것입니다.
      • Ruby On Rails에서 많이 쓰는 방식입니다.
      • 이렇게 한다면 단점은 하나입니다. JPA의 ManyToMany 형식을 못쓴다는것
      • 즉 ServiceLayer에서 데이터의 조합이 필요합니다.
      • 그러나 새로운 테이블, 모델을 만드는 비용보단 해당 비용이 가장 적을것이라 기대합니다.
      • 따라서 3번을 적용합니다.

하나의 테이블에서 Polymorphic 설계

Category와 여러 모델 간의 관계를 관리하기 위해 Polymorphic 관계를 활용한 설계를 적용하겠습니다.

CategoryRelation 모델 설계

class CategoryRelation {
  id: Long // PK
  categoryId: Long // [categories.id](http://categories.id) FK
  targetType: String // 어떤 도메인과 연결됐는지 (ex. BuddyFitFacility)
  targetId: Long // 도메인 PK
  createdAt: DateTime
  updatedAt: DateTime
}

이 설계는 다음과 같은 이점을 제공합니다:

  • 새로운 모델에 카테고리 관계가 필요할 때 새 테이블을 만들 필요가 없음
  • 하나의 테이블로 모든 카테고리 관계 관리 가능
  • 유연한 확장성 제공

사용 방법 예시

BuddyFitFacility에 카테고리를 연결하는 경우:

// 새로운 카테고리 관계 생성
val categoryRelation = CategoryRelation(
  categoryId = [category.id](http://category.id),
  targetType = "BuddyFitFacility",
  targetId = [facility.id](http://facility.id)
)

// 저장
[categoryRelationRepository.save](http://categoryRelationRepository.save)(categoryRelation)

관계 조회 예시

특정 모델의 카테고리 목록 조회:

// BuddyFitFacility 모델의 ID가 1인 항목에 연결된 모든 카테고리 조회
val categoryRelations = categoryRelationRepository.findAllByTargetTypeAndTargetId("BuddyFitFacility", 1L)
val categoryIds = [categoryRelations.map](http://categoryRelations.map) { it.categoryId }
val categories = categoryRepository.findAllById(categoryIds)

특정 카테고리와 연결된 모든 모델 조회:

// 카테고리 ID가 5인 항목과 연결된 모든 관계 조회
val relations = categoryRelationRepository.findAllByCategoryId(5L)

단점

  1. JPA의 ManyToMany 관계를 직접 사용할 수 없으므로 서비스 레이어에서 관계 매핑을 처리해야 함
  2. 타입 안전성이 부족하므로 targetType 값의 일관성을 유지하기 위한 상수나 enum 사용 권장
  3. 조회 시 JOIN이 복잡해질 수 있으므로 성능에 주의

결론

이번 공통 Category 시스템 설계의 핵심 목표는 다양한 서비스와 도메인 모델에서 반복적으로 발생하는 ‘분류/유형 관리 기능’을 하나의 통합된 구조로 효율적으로 처리하는 것입니다. 빠른 서비스 개발이라는 팀의 모토를 유지하면서도, 장기적으로 확장 가능한 구조를 만드는 것이 중요한 기준이었습니다.

그 결과, 우리는 하나의 Category 모델 + Polymorphic 기반 CategoryRelation 테이블이라는 구조를 선택했습니다. 이 방식은 다음과 같은 특징과 장점을 가집니다:

1. 개발 생산성 극대화

  • 모델마다 중간 테이블을 추가하거나 새로운 Category 모델을 만들 필요가 없습니다.
  • 새로운 서비스나 기능에서도 동일한 구조를 그대로 활용할 수 있어 개발 속도가 빨라집니다.

2. 높은 확장성과 일관성

  • 어떤 모델이든 Category를 연결할 수 있는 공통 인터페이스가 생기며, API, Repository, 검증 로직을 재사용할 수 있습니다.
  • Category 자체도 문자열 기반 key + service 구조로 유연하게 확장 가능합니다.

3. Polymorphic 구조의 장점 활용

  • 하나의 관계 테이블로 여러 모델의 카테고리 매핑을 처리하여 구조를 단순화합니다.
  • Ruby on Rails에서 널리 쓰이는 검증된 패턴으로, JPA 환경에서도 서비스 레벨 처리만 보완하면 문제없이 운영할 수 있습니다.

4. 다소의 단점은 있지만 충분히 상쇄 가능

  • JPA ManyToMany를 그대로 사용할 수 없는 점은 서비스 계층에서 매핑 처리로 보완합니다.
  • targetType의 타입 안정성 문제는 enum/상수로 일관성을 유지해 해결할 수 있습니다.
  • 조회 성능은 인덱스 설계 및 적절한 Repository 메서드 구성으로 관리 가능합니다.
공통 Category 모델화 - weggle-plus - WegglePlus Blog