1. 구현한 기능
이번에 제가 맡아 구현한 기능은 유저의 클럽 가입 흐름 전반에 대한 도메인 구조 개선입니다. 주요 작업은 다음과 같습니다:
- 유저가 가입 가능한 클럽(모임 및 스터디) 기능 구현
- 클럽의 가입 방식 다양화 및 클럽장이 직접 설정 가능하도록 설계
- 가입 양식과 가입 신청을 분리하여 독립적인 관리 구조 마련
- 향후 가입 승인, 멤버 관리, 내부 기능 확장까지 고려한 유연한 구조 설계
2. 배경
초기 설계에서는 Club, JoinRequest, JoinForm이라는 세 개의 Entity를 사용했는데요.
- 가입 양식은 가입신청서와 문제 제출용 양식을 한 테이블에 통합
- 가입 방식(JoinType)은 유저가 제출하는 요청(JoinRequest)에서 관리
이러한 구조는 도메인 흐름상 비자연적이며, 확장성이나 유지보수 측면에서도 여러 문제점을 갖고 있었습니다.
3. 요구사항
우리 팀이 설정한 클럽 가입 관련 요구사항은 다음과 같습니다:
- 클럽 생성 시, 클럽장이 가입 방식을 설정할 수 있어야 한다
- 가입 방식은 아래 네 가지 중 하나를 선택
- 즉시가입 / 일반가입 / 가입양식가입 / 문제양식가입
- 클럽장은 가입 양식 및 문제 양식을 직접 생성하고 관리
- 유저는 클럽이 정한 가입 방식에 따라 가입 신청서를 제출
- 구조는 향후 기능 확장에 유연하게 대응 가능해야 한다
4. 주요 로직
이를 바탕으로 구현한 핵심 로직은 다음과 같습니다:
- Club이 직접 가입 방식을 설정하며 (JoinType 필드로)
- 가입 방식에 따라 양식 제출 플로우를 자동 제어
- 유저는 설정된 방식에 맞춰 SubmissionForm 또는 ProblemForm을 작성하여 신청
- 클럽장은 신청서를 확인하고 승인/거절 처리 가능
- 양식 관계는 다음과 같이 설정:
- SubmissionForm: Club과 1:1
- ProblemForm: Club과 1\:N (단, 하나만 활성화 가능)
5. 의사결정 및 리팩토링 전략
1) 문제 정의 (Why)
- 책임 주체의 혼동
- JoinType이 유저의 요청에서 관리되고 있었지만, 실제 가입 방식의 주체는 클럽입니다.
- 도메인 분리 원칙 위반
- JoinForm 하나로 가입 양식과 문제 양식을 모두 처리, 역할과 책임이 불분명
- 잘못된 연관관계
- Club과 JoinForm이 1:1이 아닌 Many-to-One으로 설계되어 불일치 발생
- 확장에 비유연한 구조
- 신규 가입 방식이나 조건 추가에 대한 대응이 어려운 설계
2) 문제 분석 (What)
- 클럽이 가입 방식을 정하고, 유저는 그에 따라 요청해야 하는데 흐름이 반대로 구현
- 가입 양식과 문제 양식은 목적, 주체, 사용 시점이 완전히 다름
- 따라서 역할을 구분하고, 연관관계도 명확히 재설계할 필요가 있었습니다
3) 해결 방안 (How)
새롭게 설계한 ERD는 다음과 같습니다:
- JoinType은 JoinRequest에서 제거 → Club Entity에서 직접 관리
- 가입 양식(SubmissionForm)과 문제 양식(ProblemForm)을 명확히 분리
- 연관관계 재설정:
- Club ↔ SubmissionForm: 1:1 관계
- Club ↔ ProblemForm: 1\:N 관계 (단, 하나만 활성화)
- 확장성을 고려해 새로운 가입 방식 추가도 쉽게 대응 가능한 구조로 설계
6. 기대 효과
효과 설명
도메인 흐름의 자연스러움 | JoinType을 Club에서 관리함으로써 책임 주체가 명확해짐 |
유지보수성 향상 | 가입 양식과 문제 양식을 분리하여 각각 독립적으로 개선 가능 |
관계 명확화 | 1:1, 1\:N 관계를 명확히 설정해 JPA 매핑, DB 무결성, 가독성 향상 |
확장 용이성 확보 | 추후 가입 조건 커스터마이징, 관리자 기능 확장 등에 유리 |
7. 회고
1. 유저 시나리오를 설계에 더 강하게 반영했어야 했다
- 클럽 → 가입 방식 → 유저의 요청이라는 자연스러운 흐름이 존재하는데, 초기에 요청 객체 중심의 비직관적 구조를 설계한 것이 리팩토링의 주요 원인이 됐습니다.
2. 단기 효율보다 ‘유지보수 가능한 설계’가 중요하다
- 양식이 유사하다고 하나의 테이블로 통합한 결정은 초기에는 효율적이었지만, 결과적으로 확장성과 유지보수 비용 측면에서 더 큰 부담을 초래했습니다.
3. 설계 고민 없이 빠르게 구현하는 것이 오히려 더 느릴 수 있다
- 초기 설계를 꼼꼼히 했더라면, 이번 리팩토링과 그에 따른 테스트, 코드 수정 비용을 크게 줄일 수 있었을 것입니다.
마무리
이번 기능은 단순한 기능 구현을 넘어서, 도메인 설계의 원칙과 책임 분리의 중요성을 다시금 체감하는 계기였습니다. 앞으로도 구조적 설계와 흐름 중심의 개발을 통해 변화에 강한 시스템을 만드는 데 집중하겠습니다.
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-96일차] 문서작업 중 발견한 QueryDSL 오류 트러블슈팅 🚀🚀 (2) | 2025.07.07 |
---|---|
[내일배움캠프Spring-95일차] 동일 사용자의 빠른 중복 클릭으로 인한 중복 가입 요청 처리 실패 (0) | 2025.07.04 |
[내일배움캠프Spring-93일차] Chunk 기반을 Tasklet 기반으로 리팩토링 (0) | 2025.07.02 |
[내일배움캠프Spring-92일차] Spring Batch에서 Chunk는 왜 JdbcTemplate, Tasklet은 왜 JpaRepository를 많이 쓸까? (0) | 2025.07.01 |
[내일배움캠프Spring-91일차] Redis Redisson 기반 클럽 즉시가입 동시성 제어 구현 (0) | 2025.06.30 |