feat(inquiry): 1:1 문의 관리 시스템 구현 refs #23 #24 #25 #26 #40
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
목적
변경 요약
핵심 기능
문의 엔티티 및 도메인 모델
RESTful API 설계
비즈니스 로직 캡슐화
예외 처리 체계
주요 파일/모듈
Inquiry,Answer,InquiryCategory,InquiryStatus,InquiryFileInquiryRepository,AnswerRepositoryInquiryService,InquiryServiceImplInquiryControllerInquiryCreateRequest,AnswerCreateRequest,InquiryResponse,AnswerResponseInquiryException,ErrorCode아키텍처 설계 결정사항
1. 단일 엔티티 vs 상속 구조
결정: 단일 Inquiry 엔티티 채택
초기 설계 시 문의 타입별로 ProfileInquiry, PaymentInquiry 등으로 상속 구조를 고려했으나, 다음과 같은 이유로 단일 엔티티를 선택했습니다:
단일 엔티티를 선택한 이유:
2. 양방향 관계 설정 이유
Inquiry ↔ Answer 양방향 OneToOne 관계 채택
단방향 관계도 가능했지만, 다음과 같은 이유로 양방향 관계를 선택했습니다:
양방향 관계를 선택한 이유:
3. 연관관계 편의 메소드 패턴
엔티티 내부에 비즈니스 로직 캡슐화
Report 도메인의 설계 패턴을 참고하여, 상태 변경 로직을 엔티티 내부에 구현했습니다:
이 패턴을 선택한 이유:
4. DTO 이중 팩토리 메소드 패턴
InquiryResponse에 두 가지 변환 메소드 제공
이 패턴을 선택한 이유:
5. 예외 처리 일관성 유지
Report 도메인과 동일한 예외 처리 패턴 적용
이 패턴을 선택한 이유:
6. 카테고리 통일 전략
InquiryCategory를 FAQ 카테고리와 동일하게 설계
이 전략을 선택한 이유:
API 명세
1. 문의 생성 (POST /api/v1/inquiries)
Request Body:
{ "title": "예약 취소 문의", "contents": "예약 취소는 어떻게 하나요?", "category": "RESERVATION", "writerId": "USER-001" }Validation:
title: 필수 (NotBlank), 최대 200자contents: 필수 (NotBlank), 최대 500자category: 필수 (NotNull)writerId: 필수 (NotBlank)Response (201 Created):
{ "id": "uuid-generated", "title": "예약 취소 문의", "contents": "예약 취소는 어떻게 하나요?", "category": "RESERVATION", "status": "UNANSWERED", "writerId": "USER-001", "createdAt": "2025-10-17T10:00:00", "files": [] }2. 문의 상세 조회 (GET /api/v1/inquiries/{inquiryId})
Response (200 OK):
{ "id": "uuid", "title": "예약 취소 문의", "contents": "예약 취소는 어떻게 하나요?", "category": "RESERVATION", "status": "ANSWERED", "writerId": "USER-001", "createdAt": "2025-10-17T10:00:00", "answer": { "id": "answer-uuid", "contents": "예약 취소는 마이페이지에서 가능합니다.", "writerId": "ADMIN-001", "createdAt": "2025-10-17T11:00:00" } }3. 문의 목록 조회 (GET /api/v1/inquiries)
Query Parameters:
writerId: 작성자 ID (선택)category: 카테고리 (선택, ENUM)status: 상태 (선택, ENUM)조합 가능한 필터링:
?writerId=USER-001?category=RESERVATION?status=UNANSWERED?writerId=USER-001&status=UNANSWEREDResponse (200 OK):
[ { "id": "uuid", "title": "예약 취소 문의", "category": "RESERVATION", "status": "ANSWERED", "writerId": "USER-001", "createdAt": "2025-10-17T10:00:00" } ]설계 결정: 목록 조회는
fromWithoutAnswer()사용하여 답변 제외4. 답변 생성 (POST /api/v1/inquiries/answers)
Request Body:
{ "inquiryId": "uuid", "contents": "예약 취소는 마이페이지에서 가능합니다.", "writerId": "ADMIN-001" }Response (201 Created):
{ "id": "answer-uuid", "contents": "예약 취소는 마이페이지에서 가능합니다.", "writerId": "ADMIN-001", "createdAt": "2025-10-17T11:00:00" }부가 효과: Inquiry 상태가 UNANSWERED → ANSWERED로 자동 변경
5. 답변 확인 (PATCH /api/v1/inquiries/{inquiryId}/confirm)
Request Param:
writerId(본인 확인용)Response (200 OK)
부가 효과: Inquiry 상태가 ANSWERED → CONFIRMED로 변경
6. 문의 삭제 (DELETE /api/v1/inquiries/{inquiryId})
Request Param:
writerId(본인 확인용)Response (204 No Content)
부가 효과: Answer도 Cascade로 함께 삭제
7. 답변 삭제 (DELETE /api/v1/inquiries/{inquiryId}/answer)
Response (204 No Content)
부가 효과: Inquiry 상태가 UNANSWERED로 되돌아감
수용 기준 검증
#10 1:1 문의 등록 기능 - AC 충족
#11 1:1 문의 조회 및 관리 - AC 충족
추가 기능 요구사항
테스트 커버리지
엔티티 테스트: 22개
리포지토리 테스트: 29개
전체 테스트: 51개 모두 통과
브레이킹/마이그레이션
inquiry,answer테이블 생성테스트
단위 테스트
수동 검증 방법
성능 고려사항
1. Lazy Loading 전략
2. 인덱스 전략
3. DTO 최적화
fromWithoutAnswer()사용하여 불필요한 데이터 제외향후 작업 (Phase 2)
본 PR에서는 1:1 문의의 핵심 기능을 구현했으며, 다음 기능은 Phase 2에서 구현 예정입니다:
이미지 서버 연동 (#12)
현재 상태: InquiryFile 엔티티는 구현되어 있으나, Kafka 연동 전까지는 빈 리스트로 관리
알림 기능
조회 기능 개선
통계 기능
관리자 기능
참조