- Published on
게시판 카테고리 설계 — 단일 모델 + category 필드 vs 앱 분리
- Authors

- Name
- Hyo814
게시판 카테고리 설계 — 단일 모델 + category 필드 vs 앱 분리
카페 커뮤니티를 만들면서 게시판을 8개 카테고리로 쪼개야 했습니다. 후기, 구인, 양도, 창작, 건의, 팁, 모집, 온라인. 이걸 어떻게 모델링할지 꽤 오래 고민했고, 결국 단일 Post 모델 + category 필드 로 갔습니다. 그 과정의 트레이드오프를 정리합니다.
1. 세 가지 선택지
게시판 카테고리를 설계할 때 흔히 나오는 선택지는 세 가지입니다.
선택지 A — 카테고리별로 앱 분리
review/ models.py -> ReviewPost
job/ models.py -> JobPost
ticket/ models.py -> TicketPost
...
선택지 B — 단일 앱 + 카테고리별 서브클래스 (multi-table inheritance 또는 proxy)
class Post(models.Model): # 공통
...
class JobPost(Post): # multi-table inheritance
salary = models.IntegerField()
선택지 C — 단일 앱 + 단일 모델 + category 필드
class Post(models.Model):
CATEGORY_CHOICES = [
('review', '후기'),
('job', '구인'),
...
]
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)
title = models.CharField(max_length=200)
content = models.TextField()
...
2. 평가 기준 — 뭘 중요하게 볼 것인가
단순히 "어느 게 깔끔하냐" 가 아니라, 이번 프로젝트에 맞는 기준을 먼저 잡았습니다.
- 카테고리별 필드 차이가 얼마나 큰가?
- 전체 검색(카테고리 무관)이 필요한가?
- 카테고리 추가/삭제 빈도가 높은가?
- URL 구조가 카테고리별로 달라야 하는가?
- 권한이나 정책이 카테고리별로 크게 다른가?
이 다섯 개 기준으로 선택지를 하나씩 때려봤습니다.
3. 이번 프로젝트의 답
| 기준 | 이번 프로젝트 | 유리한 선택지 |
|---|---|---|
| 카테고리별 필드 차이 | 거의 없음 (전부 제목/본문/작성자) | C |
| 전체 검색 필요 | 있음 (헤더 검색창) | C |
| 카테고리 추가/삭제 빈도 | 낮음 (8개 고정 예정) | A, B, C 모두 가능 |
| URL 구조 | /board/<category>/ 로 통일 | C |
| 권한/정책 차이 | 미미 (관리자 공지 정도) | C |
다섯 중 네 개가 C로 몰렸습니다. 그래서 단일 모델 + category 필드로 갔습니다.
4. A(앱 분리)의 함정 — 중복의 비용
선택지 A는 처음에 가장 "깔끔해 보이는" 선택입니다. 앱 구조가 카테고리와 1:1로 맞으니 직관적이죠. 그런데 실제로 써보면 이런 일들이 생깁니다.
- 공통 템플릿(글 목록, 상세, 작성)을 8번 복붙
- 공통 뷰 로직(페이지네이션, 권한 체크)도 8번 복붙
- 전체 검색을 구현하려면 8개 모델을
UNION ALL로 합쳐야 함 - 공통 기능 추가(예: 좋아요, 북마크)가 모든 앱을 건드리는 작업이 됨
카테고리별 필드가 정말 크게 다르지 않다면, 이 중복은 빠르게 기술 부채가 됩니다. 반대로 "구인" 카테고리에만 급여, 근무지, 연락처 같은 고유 필드가 한가득 있다면 A가 정답입니다. 그런데 이 프로젝트는 그런 상황이 아니었습니다.
5. B(상속)의 함정 — ORM 복잡도
선택지 B는 "공통은 Post에 두고, 카테고리별 필드는 서브클래스에" 라는 깔끔한 OOP 접근입니다. Django에서는 이걸 multi-table inheritance로 지원합니다.
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class JobPost(Post):
salary = models.IntegerField()
단점:
- JOIN이 기본입니다.
JobPost.objects.all()이Post와JobPost테이블을 조인합니다. 작을 때는 괜찮지만 테이블이 커지면 성능에 영향. - 전체 글 목록을 얻으려면
Post.objects.all()을 돌리는데, 이때 서브클래스 필드는 접근할 수 없음. 필요하면select_related로 끌어와야 합니다. - 마이그레이션이 번잡. 서브클래스 모델 추가마다 FK가 새로 생성.
OOP적으로는 예쁘지만, Django ORM이 실제로 돌아가는 방식과 정합성이 떨어집니다. "이론적으로 맞는" 설계와 "현실적으로 편한" 설계가 엇갈리는 지점이죠.
6. C(단일 모델)의 트레이드오프 — 인정하고 가져간 것
단일 모델을 택했을 때 내가 감수하겠다고 결정한 단점도 분명히 있었습니다.
6.1 필드 분기가 생기면 지저분해진다
나중에 "구인 카테고리만 급여 필드를 받고 싶다" 는 요구가 생기면, 단일 모델에서는 extra_data(JSON 필드)나 별도 테이블로 도망가야 합니다. 이때가 바로 B나 A로 마이그레이션해야 하는 시점 입니다.
6.2 카테고리 목록이 코드에 박혀 있다
CATEGORY_CHOICES 가 상수로 박혀 있어서, 새 카테고리 추가하려면 코드 수정 + 마이그레이션이 필요합니다. 카테고리를 런타임에 바꿀 수 있으려면 별도 Category 테이블로 빼야 합니다.
이번에는 카테고리를 자주 안 바꿀 거라 상수로 두는 게 편했습니다. 관리자가 직접 카테고리를 추가하는 서비스였다면 테이블로 뺐을 겁니다.
6.3 URL 라우팅 한 곳에서 전부 분기
# board/urls.py
urlpatterns = [
path('<str:category>/', views.board_list, name='list'),
path('<str:category>/write/', views.board_write, name='write'),
path('<str:category>/<int:pk>/', views.board_detail, name='detail'),
]
URL 한 곳에 카테고리가 들어가고, 뷰에서 category를 받아 필터링합니다. 유효하지 않은 카테고리는 404로 보냅니다.
VALID_CATEGORIES = dict(Post.CATEGORY_CHOICES)
def board_list(request, category):
if category not in VALID_CATEGORIES:
raise Http404()
posts = Post.objects.filter(category=category)
...
7. "확장성" 의 유혹에 지지 않기
설계할 때 가장 흔한 실수가 "나중에 바뀔 수도 있으니 지금 유연하게 만들자" 입니다. 8개 카테고리가 80개가 될 수도 있고, 각 카테고리가 완전히 다른 필드를 가질 수도 있고… 이런 "혹시" 를 전부 대비하면 YAGNI 원칙을 위반한 오버엔지니어링이 됩니다.
이번 프로젝트의 기준:
- 카테고리가 정말로 런타임에 추가돼야 하는가? → 아니요
- 카테고리별 필드가 정말로 크게 다른가? → 아니요
- 운영 중에 스키마가 정말로 자주 바뀔 것 같은가? → 아니요
세 개 다 "아니요" 면 단일 모델이 맞습니다. 나중에 정말로 필요해지면 그때 마이그레이션하면 됩니다. "나중의 가능성"을 위해 지금의 복잡도를 쓰지 않는다.
8. 체크리스트 — 단일 모델이 적합한지 판정
다음 프로젝트에서도 쓸 수 있도록 체크리스트로 정리했습니다.
- 카테고리별 공통 필드 비율이 80% 이상인가?
- 전체 검색(카테고리 무관)이 자주 필요한가?
- 카테고리 추가 빈도가 낮은가 (연 1~2회 이하)?
- URL 구조가 카테고리 파라미터 하나로 충분한가?
- 카테고리별 권한 정책이 동일하거나 간단한 분기로 처리 가능한가?
4개 이상 예스면 단일 모델이 편합니다. 2개 이하면 앱 분리를 검토할 타이밍입니다.
정리
- 필드 차이가 작고 공통 화면이 많은 게시판은 단일 모델 + category 필드가 제일 싸고 단순
- 상속(B) 은 OOP적으로 예쁘지만 ORM 복잡도가 늘어 실익이 적음
- 앱 분리(A) 는 카테고리별 필드 차이가 클 때만
- "나중의 확장성" 을 핑계로 지금 복잡도를 키우지 않는다
- 단일 모델의 한계(필드 분기, 런타임 카테고리)가 실제로 닥치면 그때 마이그레이션