오늘 한 일 요약
- 카테고리 생성 — <<POST /api/categories>> 구현, 상위 존재·이름 중복 검증 후 <<sortOrder>> 기본 0으로 저장.
- 카테고리 수정 — <<PATCH /api/categories/{id}>> 구현, <<JsonNullable>> 로 부분 업데이트 지원·깊이 / 중복 예외 처리.
- 테스트 환경 분리 — <<TestBase>> 베이스 + JUnit 확장으로 모든 테스트를 <<test>> 프로필로 강제.
카테고리 생성
개요
- <<POST /api/categories>> 엔드포인트를 구현해 최상위·하위(1 단계) 카테고리를 등록할 수 있게 했다.
- 입력 필드: <<name>>, <<parentId>> (nullable), <<sortOrder>> (nullable·기본값 0).
고민한 점
- 전달된 <<parentId>>가 실제로 존재하는지 서비스 레이어에서 검증할 방법.
- 카테고리 이름 중복을 어떤 범위(전역 vs 동일 부모)에서 제한할지 기준 설정.
- 검증 실패 시 예외를 어디서 던지고 어떤 HTTP 상태로 변환할지.
- <<sortOrder>>를 요청에 포함하지 않으면 어떤 값으로 저장할지(디폴트 0 확정).
해결 방법
- 상위 카테고리 존재 검증
- <<parentId>>가 null이 아니면 <<findById>>로 조회하고, 없으면 <<NotFoundException>> 발생.
- 이름 중복 검증
- “동일 부모 내에서만 유니크”로 정의.
- 레포지터리 메서드 <<existsByNameAndParentId>>로 사전 체크 후 중복이면 <<DuplicateCategoryException>> 발생.
- DB에도 <<UNIQUE (parent_id, name)>> 제약을 걸어 이중 방어.
- <<sortOrder>> 처리
- 값이 없으면 서비스에서 0으로 설정해 저장(정렬 로직은 조회 단계에서 별도 구현 예정).
- 예외 처리
- 서비스 레이어에서 발생한 예외를 글로벌 핸들러가 받아 <<400 Bad Request>>로 변환해 응답.
결과
- 정상·예외(부모 미존재, 이름 중복) 시나리오를 포함한 단위 테스트 통과.
- 기본 <<sortOrder>> 0이 일관되게 저장돼 이후 조회·정렬 로직 연계 준비 완료.
카테고리 수정
개요
- <<PATCH /api/categories/{id}>> 엔드포인트를 구현해 카테고리의 <<name>>, <<parentId>>, <<sortOrder>> 중 필요한 항목만 갱신할 수 있게 했다.
- 입력 DTO는 <<JsonNullable>> 기반으로 “값 있음 / 없음”을 명확히 구분한다.
고민한 점
- 일부 필드만 변경할 때 <<null>> 과 “값 미전달”을 구분하지 못하면 의도치 않은 덮어쓰기가 발생한다.
- <<JsonNullable>> 사용 시 모듈 미등록으로 <<HttpMessageConversionException>> 이 발생했다.
- 상위 카테고리를 바꿀 때 “자기 자신을 부모로 지정”하거나 “깊이 2단계를 초과”하는 요청을 어떻게 막고 어떤 상태 코드로 응답할지.
- 이름을 수정할 때 동일 부모 내 중복 검사를 다시 수행해야 한다.
- 변경이 실제로 없으면 DB에 불필요한 UPDATE가 나가지 않게 해야 한다.
해결 방법
- DTO 설정
- 모든 필드를 <<JsonNullable<T>>>로 선언.
- <<ObjectMapper>>에 <<NullablesModule>>을 등록해 직‧역직렬화 오류를 해결.
- 부분 업데이트 로직
- 서비스 레이어에서 <<if (request.name().isPresent())>> 패턴으로 필드별 적용.
- 변경 내용이 전혀 없으면 204 No Content 응답.
- 상위 카테고리 검증
- <<parentId>>가 있을 경우 <<findById>>로 존재 여부 확인 → 없으면 400 Bad Request 예외.
- id == parentId 또는 깊이 초과(2단계 이상) 요청도 동일한 400 Bad Request 예외로 통일.
- 깊이 초과 예외의 상태 코드를 잠시 고민했지만, “클라이언트가 잘못 보낸 요청”에 해당하므로 400으로 확정했다.
- 이름 중복 검증
- 동일 부모 내에서만 유니크하도록 <<existsByNameAndParentId>>로 사전 체크.
- 중복이 발견되면 409 Conflict 예외를 던져 상태-코드별 예외 체계에 맞춘다.
- 트랜잭션·감사
- <<@Transactional>> 범위 내에서 변경 감지로 UPDATE.
- JPA 감사 컬럼에 수정자·수정 시각 자동 기록.
결과
- 정상 케이스와 예외 케이스(부모 미존재 400, 깊이 초과 400, 이름 중복 409)를 포함한 단위 테스트 14건 통과.
- 모듈 미등록으로 발생하던 <<HttpMessageConversionException>> 재발 없음.
테스트 환경 분리
개요
모든 통합 테스트가 <<test>> 프로필로 동작하도록 <<TestBase>> 추상 클래스를 만들고, 여기서 공통 어노테이션을 선언했다.
- <<@ActiveProfiles( "test" )>>
- <<@SpringBootTest>>
- <<@Transactional>>
고민한 점
- 새 테스트 클래스를 만들 때 << TestBase >> 상속을 깜빡하면 여전히 개발 DB를 사용해 PK가 증가한다.
- IDE·CI 어디서 실행해도 “프로필 누락”을 즉시 감지해 테스트를 중단시키고 싶다.
해결 방법
- << TestBase >> 추상 클래스
- 모든 테스트 코드가 이 클래스를 상속하도록 가이드.
- JUnit 5 확장 <<RequireTestProfileExtension>> 작성
- <<BeforeAllCallback>> 단계에서 스프링 컨텍스트의 활성 프로필을 조회.
- <<test>> 가 아니면 <<Exception>> 을 던져 실행을 즉시 중단하고,
“활성 프로필이 ‘test’가 아닙니다.” 라는 메시지를 출력.
- 서비스 로더 등록
- <<src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension>> 파일에
<<com.khmall.support.RequireTestProfileExtension>> 를 기입해 전역 확장으로 자동 적용.
- <<src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension>> 파일에
결과
- 모든 통합 테스트가 항상 <<test>> 프로필로 실행돼 개발 DB 오염이 사라졌다.
- 상속을 빼먹은 테스트는 0.2 초 이내에 중단되며 명확한 안내 메시지를 출력해 휴먼 에러를 방지한다.
- 추가 JVM 옵션이나 <<spring.profiles.active>> 설정 없이도 IDE, Gradle, CI에서 동일하게 동작한다.
하루 회고
배운 점
- 입력 / 이름 중복 검증을 서비스 층에 둬 로직과 컨트롤러를 분리할 수 있었다.
- <<JsonNullable>> 는 모듈 등록이 필수, 더티 체킹 덕분에 “변경 없음”은 DB 부하가 없다.
- JUnit 확장으로 프로필 누락을 즉시 중단해 휴먼 에러를 줄였다.
다음 할 일
- 카테고리 조회(정렬·트리 응답)
- 카테고리 삭제(하위 노드 정책 결정)
- 상품 CRUD 구현
'My Project > 쇼핑몰' 카테고리의 다른 글
| 작업 일지 #6 상품 등록, 이미지 등록 (AWS S3 사전 서명 방식) (2) | 2025.08.13 |
|---|---|
| AOP + MDC 기반 감사 로그 (2) | 2025.08.06 |
| JsonNullable을 활용한 PATCH 요청 DTO 구현 (1) | 2025.08.05 |
| Trouble shooting #4 상위 카테고리를 해제하지 못하는 문제 (0) | 2025.08.05 |
| Trouble shooting #3 트랜잭션 롤백 후 AUTO_INCREMENT 증가 (0) | 2025.08.05 |