Skip to content

⚙️[기능추가][탐험] 행성/지역 해금 도메인 구현 #27

Open

Description

@Cassiiopeia

📝 현재 문제점

  • 탐험 시스템 관련 도메인이 전혀 구현되어 있지 않음
  • API 스펙상 행성 목록 조회, 지역 목록 조회, 지역 해금, 행성 해금 4개 엔드포인트가 정의되어 있으나 모두 미구현
  • 행성/지역 마스터 데이터(시드)를 DB에 관리해야 함

🛠️ 해결 방안 / 제안 기능

  • 행성(Planet) 및 지역(Region)을 단일 ExplorationNode 테이블로 트리 구조 관리 (nodeType 컬럼으로 구분)
  • GET /api/explorations/planets : 유저 해금 상태와 함께 행성 목록 반환
  • GET /api/explorations/planets/{planetId}/regions : 해당 행성의 지역 목록 + 유저 해금 상태 반환
  • POST /api/explorations/regions/{regionId}/unlock : 연료 소비 후 지역 해금. 행성의 모든 지역 해금 시 자동 행성 클리어
  • POST /api/explorations/planets/{planetId}/unlock : 연료 소비 후 행성 해금
  • 지구는 시드 데이터로 기본 해금 상태 설정

⚙️ 작업 내용

  • ExplorationNode Entity (행성/지역 마스터), UserExploration Entity (유저별 해금 상태) 구현
  • Repository 및 시드 데이터 Flyway 마이그레이션 작성
  • ExplorationService : 목록 조회, 해금 로직 (연료 차감 + 해금 상태 insert + 행성 자동 클리어 판정) 구현
  • FuelService.consume() 연동 (트랜잭션 내에서 원자적 처리)
  • SS-Web/controller/exploration/ExplorationController 구현
  • Flyway 마이그레이션 : exploration_nodes, user_explorations 테이블 + 시드 데이터 추가

🙋‍♂️ 담당자

  • 백엔드: 이름
  • 프론트엔드: 이름
  • 디자인: 이름

Activity

  1. self-assigned this
    on Apr 22, 2026
  2. EM-H20 commented on Apr 22, 2026

    @EM-H20
    Contributor

    Guide by SUH-LAB

    브랜치

    20260422_#27_행성_지역_해금_도메인_구현
    

    커밋 메시지

    행성/지역 해금 도메인 구현 : feat : {변경 사항에 대한 설명} https://github.com/SpaceStudyShip/SpaceStudyShip-BE/issues/27
    
  3. added 17 commits that reference this issue on May 29, 2026
  4. EM-H20 commented on May 29, 2026

    @EM-H20
    Contributor

    행성/지역 해금(탐험) 도메인 구현 (#27)

    📌 작업 개요

    우주 탐험 도메인을 신규 구현. 행성(planet) → 지역(region) 2단계 트리를 연료로 해금하며, 이전 행성을 클리어해야 다음 행성을 해금할 수 있는 진행 게이트를 포함. 시드 데이터는 프론트(Flutter) 게스트 시드와 1:1로 일치시켜 게스트/회원 모드 간 정합성 확보. 구현 검증 중 Flyway가 실제로는 한 번도 실행되지 않던 잠복 인프라 버그를 발견·수정.

    🎯 구현 목표

    • 행성 목록 / 지역 목록 조회 + 지역 해금 / 행성 해금 4개 엔드포인트
    • 연료 차감과 해금을 한 트랜잭션에서 원자적으로 처리 (별도 fuel API 호출 불필요)
    • 행성 진행 게이트(선행 행성 클리어 필수)
    • 프론트 계약(docs/api-specs/exploration-frontend-requirements.md) 충족

    ✅ 구현 내용

    API 엔드포인트 (SS-Web)

    • 파일: SS-Web/.../controller/exploration/ExplorationController.java
    • 변경 내용: /api/explorations 하위 4개 엔드포인트
      • GET /planets — 행성 목록 + 유저 해금/클리어/진행도
      • GET /planets/{planetId}/regions — 행성 하위 지역 목록 + 해금 상태
      • POST /regions/{regionId}/unlock — 지역 해금
      • POST /planets/{planetId}/unlock — 행성 해금
    • 이유: 컨트롤러는 SS-Web에만 위치하는 컨벤션 준수, 모든 동작은 서비스로 위임

    마스터/진행 엔티티 (SS-Study)

    • 파일: study/exploration/entity/ExplorationNode.java, UserExploration.java
    • 변경 내용:
      • ExplorationNode: 행성/지역 공통 마스터(시드 전용, 읽기 전용). nodeType, prerequisiteNodeId(선행 행성), requiredFuel, mapX/Y 등
      • UserExploration: 유저별 해금 상태. 행이 존재하면 해금됨을 의미. UNIQUE(user_id, node_id)로 중복 해금 방지
    • 이유: 트리 구조를 단일 노드 테이블로 표현, 유저 상태는 별도 테이블로 분리

    해금/조회 로직 (SS-Study)

    • 파일: study/exploration/service/ExplorationService.java
    • 변경 내용:
      • 조회: 행성 클리어/진행도(clearedChildren/totalChildren/progressRatio)를 저장하지 않고 조회 시 파생 계산
      • 기본 해금: requiredFuel == 0인 노드(지구, 대한민국)는 행 없이도 암묵적으로 해금 처리
      • 해금: REGION_NOT_FOUND/PLANET_LOCKED/ALREADY_UNLOCKED 검증 → 연료 잔량 pre-check → FuelService.consume → 해금 행 저장 → (지역) 상위 행성 자동 클리어 판정
      • 행성 해금: 선행 행성이 클리어 안 됐으면 PREREQUISITE_NOT_CLEARED
    • 이유: 클리어 상태를 파생 계산해 drift 없이 항상 일관. 해금 메서드를 @Transactional로 두어 FuelService.consume와 동일 트랜잭션에 묶음 → 연료 차감/거래내역/해금이 원자적

    DTO (SS-Study)

    • 파일: study/exploration/dto/ — PlanetResponse, RegionResponse, ProgressDto, UnlockedNodeDto, RegionUnlockResponse, PlanetUnlockResponse (record)
    • 변경 내용: nodeType는 소문자(planet/region) 직렬화, 행성 응답에 prerequisiteId·progress 포함, 해금 응답에 currentFuel 포함(프론트가 fuel 별도 조회 불필요)

    에러 처리 + INSUFFICIENT_FUEL 응답 보강 (SS-Common)

    • 파일: common/exception/ErrorCode.java, ErrorResponse.java, InsufficientFuelException.java, GlobalExceptionHandler.java
    • 변경 내용:
      • 탐험 에러 5종 추가(PLANET_NOT_FOUND, REGION_NOT_FOUND, ALREADY_UNLOCKED, PLANET_LOCKED, PREREQUISITE_NOT_CLEARED)
      • ErrorResponse에 nullable requiredFuel/currentFuel 추가 + @JsonInclude(NON_NULL) → 연료 부족 시에만 해당 필드 노출, 기존 에러 응답 형식은 불변
    • 이유: 프론트가 "필요/보유 연료" 안내 문구를 만들 수 있도록 연료 부족 응답에 수치 동봉

    시드 데이터 (마이그레이션)

    • 파일: SS-Web/.../db/migration/V0_0_42__add_exploration.sql
    • 변경 내용: exploration_nodes, user_exploration_progress 테이블 + 시드 38노드(행성 8 + 지역 30)
      • 행성: 지구·수성·금성·화성·목성·토성·천왕성·해왕성 (연료 0/3/5/10/20/30/45/60, 선행 체인)
      • 지역: 지구 12개(국가코드 아이콘) + 그 외 18개(행성이름 아이콘)
    • 이유: 프론트 게스트 시드(exploration_seed_data.dart)와 id/아이콘/연료/정렬을 row-by-row로 동일하게 맞춰 게스트/회원 코드·렌더링 재사용

    🔧 주요 변경사항 상세

    Flyway 미실행 인프라 버그 수정 (중요)

    구현 후 GET /planets가 빈 배열을 반환하는 현상 발생. 원인 추적 결과:

    • dev DB에 exploration_nodes 테이블은 있으나 0행, flyway_schema_history 테이블 자체가 부재 → Flyway가 한 번도 실행된 적 없음
    • flyway-core는 클래스패스에 있으나, Spring Boot 4가 autoconfiguration을 기술별 모듈로 분리하면서 Flyway 연동 모듈(spring-boot-flyway)이 누락됨. Boot 3에선 flyway-core만으로 자동 실행됐으나 Boot 4에선 이 모듈이 있어야 FlywayMigrationInitializer가 작동
    • 그동안 스키마는 전부 Hibernate ddl-auto=update가 생성, 마이그레이션 5개는 모두 무력 상태였음(다른 도메인은 시드가 없어 표면화되지 않음)

    수정:

    • 파일: SS-Common/build.gradle — org.springframework.boot:spring-boot-flyway 의존성 추가
    • 파일: SS-Web/.../application.yml — baseline-version: 0 추가
      • 이미 비어있지 않은 DB에서 baseline-on-migrate의 기본 baseline(버전 1)이 0.0.x 마이그레이션을 "이미 적용됨"으로 스킵하는 문제 방지. 0으로 두어 0.0.31~0.0.42 전부 적용되게 함

    특이사항:

    • 기존 Hibernate가 만든 테이블은 마이그레이션의 CREATE TABLE IF NOT EXISTS로 건너뛰므로, 마이그레이션에 정의된 CHECK/FK 제약이 그 테이블엔 안 붙어 있을 수 있음(Hibernate-우선 설정의 기존 특성, 시드 동작엔 영향 없음)
    • production은 다음 배포 시 Flyway가 baseline(0) 후 마이그레이션 적용 + 시드. IF NOT EXISTS/ON CONFLICT DO NOTHING이라 기존 데이터에 안전

    기본 해금 / 진행 게이트

    • requiredFuel == 0 노드는 진행 행 없이 암묵 해금 → 지구/대한민국이 시작 시점부터 열림
    • 행성 해금은 선행 행성의 모든 하위 지역이 해금(=클리어) 돼야 가능. 진행 스파인이 자연스럽게 형성됨

    📦 의존성 변경

    • org.springframework.boot:spring-boot-flyway 추가 (Spring Boot 4 Flyway autoconfiguration 모듈)

    🧪 테스트 및 검증

    • 단위/통합 테스트 추가: 엔티티, 리포지토리(Testcontainers PostgreSQL — Converter 라운드트립 + UNIQUE 제약 위반), 서비스(조회 파생/해금/자동 클리어/선행 게이트/연료 부족/각 에러 ErrorCode 검증), 컨트롤러(MockMvc 4개 엔드포인트 + 에러 매핑 + INSUFFICIENT_FUEL 본문 검증)
    • SS-Common/SS-Study/SS-Web 전체 테스트 통과 확인
    • 실제 dev DB에 Flyway migrate 실행으로 시드 적용 검증: exploration_nodes planet=8, region=30(38노드), 행성 순서 earth, mercury, venus, mars, jupiter, saturn, uranus, neptune 확인

    🔄 코드 리뷰(CodeRabbit) 반영

    PR #42 자동 리뷰 12건을 점검·triage하여 실제 결함만 수정(커밋 a24577a).

    🔴 무료 지역(requiredFuel=0)으로 인한 행성 클리어 영구 불가 — 수정

    • 문제: getRegions는 requiredFuel==0 지역(예: korea)을 암묵 해금/클리어로 취급하나, getPlanets의 진행도 집계와 isPlanetCleared는 진행도 레코드가 있는 지역만 카운트. 무료 지역은 진행도 레코드가 절대 생성되지 않으므로(해금 시 ALREADY_UNLOCKED), 무료 지역을 가진 지구가 영원히 클리어되지 않음 → 수성 해금이 영구 차단(PREREQUISITE_NOT_CLEARED). 게임 진행 불가
    • 파일: study/exploration/service/ExplorationService.java
    • 수정: clearedByParent 집계와 isPlanetCleared 모두 requiredFuel == 0 || unlocked.contains(id)로 보정 → getRegions와 일관되게 무료 지역을 클리어로 인정
    • 테스트 보강: unlockPlanet_success에서 무료 지역을 진행도 맵에 넣어 결함을 가리던 픽스처를, 진행도 없이도 클리어 인정되는 실제 상태로 교정

    반박/유지 (리뷰 도구가 맥락 미보유)

    • 해금 응답 201 제안: 프론트 계약이 200 OK로 고정 → 200 유지
    • 커밋 #27 포맷 지적: 짧은 이슈 태그(#27)는 사용자 명시 선호 → 유지
    • plan 문서 커밋 단계 지적: 승인된 워크플로우 절차 기술일 뿐 → 유지

    보류 (별도 처리 권장)

    • unlockedAt 타임존: LocalDateTime.now()를 UTC로 직렬화 → JVM TZ가 UTC 아니면 오프셋 가능. 단, fuel/timer/todo 등 프로젝트 전체 공통 컨벤션이라 탐험만 고치면 불일치. 서버 TZ를 UTC로 고정하는 전역 개선으로 별도 처리 권장

    문서 정리

    • spec/plan의 로컬 절대경로 → 레포 상대경로
    • exploration-frontend-requirements.md §8.2 비교표에 "구버전 스냅샷, 본 PR에서 해소" 주석 추가

    📌 참고사항

    • 게스트(로컬)와 회원(서버)은 완전히 분리. 게스트→회원 마이그레이션은 범위 밖
    • 동기화 Tier 2(Server-Validated): 해금은 온라인 필수, 서버에서 잔량 확인·차감·해금 원자 처리
    • API 스펙 문서(docs/api-specs/05_exploration.md)를 구현에 맞춰 갱신(prerequisiteId, PREREQUISITE_NOT_CLEARED, 시드 값/아이콘 규칙)
    • 서버 직접 실행: SPRING_PROFILES_ACTIVE=dev ./gradlew :SS-Web:bootRun
  5. added
    작업완료작업 완료 상태인 경우 (이슈 폐쇄)
    and removed
    작업전작업 시작 전 준비 상태
    on May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

작업완료작업 완료 상태인 경우 (이슈 폐쇄)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions