Skip to content

T-35 트랙 필드 승격 (member.track enum → 참조 테이블) - #58

Open
ff1451 wants to merge 1 commit into
feat/t04-local-envfrom
feat/t35-track-promotion
Open

T-35 트랙 필드 승격 (member.track enum → 참조 테이블)#58
ff1451 wants to merge 1 commit into
feat/t04-local-envfrom
feat/t35-track-promotion

Conversation

@ff1451

@ff1451 ff1451 commented Aug 19, 2026

Copy link
Copy Markdown

Refs #54
Base: feat/t04-local-env (#57) — T-01/T-04 위에 stack.

배경 (ADR-002)

member.track은 테이블이 아니라 VARCHAR(20) CHECK 제약 + Java enum이었다. 값은 10종인데 홈페이지에 있는 SECURITY가 빠져 있었다. 전 코드에서 이 enum이 분기(switch/if)에 쓰이는 곳은 0개 — 참조는 엔티티 필드·응답 DTO의 .name()·요청 DTO·테스트 픽스처뿐이었다. 반면 요구사항(FR-1.1, 운영진이 트랙을 추가할 수 있어야 함)은 런타임에 집합이 바뀌어야 한다고 요구한다. → 네 가지 판단 기준(변경 시점·행동 여부·속성·관계)이 전부 참조 테이블을 가리켜서 승격했다.

마이그레이션 (expand → migrate, contract는 범위 밖)

-- V7: track 테이블 + 11값(기존 10 + SECURITY)
-- V8: member.track_id 추가 → code 기준 백필 → NOT NULL + FK

주의 깊게 본 지점 (advisor 리뷰에서 나옴): V8에서 member.track_id를 NOT NULL로 승격하면서 새 TrackMaster 매핑이 레거시 track 컬럼을 더 이상 다루지 않으므로, 그 컬럼의 기존 NOT NULL을 풀지 않으면 모든 신규 부원 생성 INSERT가 실패한다. 같은 마이그레이션에 ALTER TABLE member ALTER COLUMN track DROP NOT NULL을 포함했다. CHECK 제약(ck_member_track)은 NULL을 통과시키므로 충돌 없다.

클래스명: Track이 아니라 TrackMaster

member.model.Track(API 요청/응답에 쓰는 코드 enum, 그대로 유지)과 새 JPA 엔티티가 같은 이름이면, 두 타입을 함께 참조해야 하는 곳(요청 DTO → 엔티티 변환)에서 import 충돌이 난다. 엔티티를 TrackMaster로 명명해 해결했다 — 문서에서 이미 이 개념을 "트랙 마스터"로 불러 왔던 것과도 맞는다. DTO 계약은 바뀌지 않는다: AdminMemberCreateRequest.track은 여전히 Track enum이고, 서비스 레이어에서 TrackMasterRepository.findByCode(track.name())로 엔티티를 조회한다.

변경

  • TrackMaster 엔티티 + TrackMasterRepository (track/model, track/repository)
  • Member.track: Track enum → @ManyToOne TrackMaster
  • AdminMemberService: 요청의 enum을 엔티티로 변환하는 조회 추가. 못 찾으면 TrackException(TRACK_NOT_FOUND) — T-01에서 만든 스켈레톤의 첫 실제 호출부다.
  • MemberResponse/LoginResponse: getTrack().name()getTrack().getCode() (응답 값은 동일한 문자열)
  • 기존 통합 테스트 6개(auth 4, member 2)의 Track.BACKEND 픽스처를 trackMasterRepository.findByCode("BACKEND")로 교체

테스트

TrackPromotionIntegrationTest(신규, IntegrationTestSupport 두 번째 소비자)

AC 검증
AC-8.1 track 11행
AC-8.2 신규 부원의 track_id가 null 아님
AC-8.3 member ⋈ track 정합성 쿼리 0건
AC-8.4 레거시 track 컬럼이 nullable + 구버전 스타일 INSERT(track_id 없이)는 실패한다는 caveat를 명시적으로 테스트로 남김

AC-8.4는 "구버전 앱이 계속 부팅·로그인된다"는 읽기 경로 주장이다. 쓰기 경로(신규 부원 생성)는 신버전 배포 후에만 안전하다 — 이 프로젝트는 이미 순차 재시작으로 수초 다운타임을 감수하고 있어 문제는 아니지만, 조용히 넘어가지 않고 테스트로 박아 뒀다.

기존 auth 4 + member 2 통합 테스트는 그대로 통과 → AC-8.5(부원 생성 API가 code 문자열로 계속 동작), AC-8.6(기존 테스트 통과)을 겸해서 검증한다.

./gradlew test34개 전체 통과, 실패 0. TrackPromotionIntegrationTestHealthCheckIntegrationTest(T-01)와 Hikari pool을 공유하는 게 로그로 확인돼 IntegrationTestSupport의 싱글턴 컨테이너/컨텍스트 캐싱이 실제로 동작함도 함께 검증됐다.

하지 않은 것

  • contract 단계(레거시 track 컬럼·CHECK 제약 완전 제거) — 배포 안정화 후 별도 진행
  • /v1/admin/tracks(트랙 마스터 CRUD) — T-07 범위

member.track이 문자열 enum이고 SECURITY가 없어서 홈페이지 CMS가 트랙을 참조할 수
없었다. 참조 테이블로 승격한다. expand -> migrate, contract는 범위 밖.

- V7__create_track.sql — track 테이블 + 11값 시드(기존 10 + SECURITY)
- V8__add_member_track_id.sql — track_id 컬럼 추가 → code 기준 백필 → not null
  + FK 승격. 레거시 track 컬럼은 남기되 NOT NULL을 푼다(신버전 엔티티가 그 컬럼을
  매핑하지 않으므로, 안 풀면 신규 INSERT가 실패한다)
- TrackMaster 엔티티(track/model) — 클래스명을 Track이 아니라 TrackMaster로 지었다.
  member.model.Track(API 요청/응답 코드 enum)과 이름이 겹치면 두 타입을 함께
  참조하는 파일(DTO→엔티티 변환)에서 import 충돌이 난다
- Member.track: Track enum → @manytoone TrackMaster
- AdminMemberService: request.track()(enum)을 TrackMasterRepository로 조회해
  엔티티로 변환 후 저장. DTO 계약은 그대로 code 문자열 유지
- MemberResponse/LoginResponse: track().name() → track().getCode() (응답 값 동일)
- 기존 통합 테스트 6개의 Track.BACKEND 픽스처를
  trackMasterRepository.findByCode("BACKEND")로 교체

테스트:
- TrackPromotionIntegrationTest(신규, IntegrationTestSupport 두 번째 소비자) —
  AC-8.1(11행) · AC-8.2(track_id not null) · AC-8.3(백필 정합성 0건) ·
  AC-8.4(레거시 컬럼 nullable + 구버전 스타일 INSERT는 실패한다는 caveat)
- 기존 auth 4 + member 2 = AC-8.5(부원 생성 API 유지) · AC-8.6(기존 테스트 통과)
  겸 검증
- ./gradlew test 전체 통과(34개, 실패 0). IntegrationTestSupport가 새 테스트와
  기존 HealthCheckIntegrationTest 사이에서 Spring 컨텍스트/컨테이너를 실제로
  공유하는 것도 이번에 확인됐다(Hikari pool 로그가 늘지 않음).

AC-8.4는 "구버전 앱이 계속 부팅되고 로그인된다"는 읽기 경로 주장이다. 쓰기 경로
(신규 부원 생성)는 신버전 배포 후에만 안전하다 — 순차 재시작으로 수초 다운타임을
이미 감수하는 프로젝트라 문제 없지만, 명시적으로 caveat 테스트를 남겼다.

Refs #54
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a7d0914-2317-44a5-838d-92e248a4447d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant