Skip to content

PR 테스트#116

Closed
CheatIsKey wants to merge 57 commits into
mainfrom
develop
Closed

PR 테스트#116
CheatIsKey wants to merge 57 commits into
mainfrom
develop

Conversation

@CheatIsKey
Copy link
Copy Markdown
Member

No description provided.

CheatIsKey and others added 30 commits April 8, 2026 14:44
* Feat: Payment Entity and 3 Layer Architecture Package Base classes

* Fix: API 경로 수정

* Fix:Order 엔티티와 연관관계 삭제 후 fk만 사용하도록 변경"

* Fix: Payment, Payment Controller 오류 수정

* Refactor: Payment 컬럼 length 추가

* Refactor : 자잘한 오류 수정
[feat] 상품 엔티티 세팅 및 기타 초기 세팅
* Feat:Refund Entity and 3 Layer Architecture Base Classes

* Refactor : Status에 length 설정 삭제

* Fix: BaseEntity 추가

* Refactor : @NoArgsConstructor 삭제, 필드명 변경

* Refactor : import 제거

* Refactor : 코드리뷰사항 변경
feat: Order 엔티티와 3-Layer-Architecture 틀 구현
feat: Jwt와 Spring Security 적용
 - 단순히 Response Body에 accessToken을 담고 refreshToken을 쿠키에 담는 게 아닌, 추후 OAuth 확장성(리다이렉트 콜백)을 고려해 둘 다 쿠키에 담아서 전달
 - 로그인을 시도하면 accessToken과 refreshToken을 발급
 - 로그인 상태에서 활동하다가 accessToken이 만료되면, Silent-Refresh로 고객이 모르게 재발급
 - 로그아웃을 시도하면 accessToken과 refreshToken을 둘 다 폐기
config: QueryDSL 의존성 주입 및 환경설정
 - QueryDSL 의존성 (OpenFeign fork - Hibernate 6 / Spring Boot 3.x 완전 호환)
 - 원본 com.querydsl:5.0.0은 Hibernate 6에서 NoClassDefFoundError 발생 (사실상 유지보수 중단)
 - io.github.openfeign.querydsl은 기존 API(com.querydsl.* 패키지명)를 유지하므로 코드 변경 불필요
* feat: User도메인 마이페이지 조회 및 내 정보 수정 기능구현

* chore: AI 코드리뷰 내용 반영하여 수정
* feat: Order 엔티티 개발 및 3-Layer-Architecture 틀 구성
* Refactor: 인증/인가에서 사용하지 않는 메서드 삭제 및 주석 추가
* Refactor: OrderUser 엔티티 개발
* feat:상품 목록 조회, 상세 조회 개발, 더미데이터 3개 추가
* Feat: 주문 생성 API
 - 상품 존재 여부, 판매 가능 여부, 재고 수량을 사전 검증
 - 주문 총액은 서버에서 계산
 - ODN-YYYYMMDD-{NANOID}
 - 주문 정책에서 잘못 정리된 주석 수정
 - Dto 클래스 이름을 컨벤션에 맞춰서 수정
* feat: User도메인 마이페이지 조회 및 내 정보 수정 기능구현
* Feat: 주문 목록 조회 API
 - 본인이 주문한 목록 조회
 - 커서 기반 Slice 무한 스크롤
* feat: User도메인 마이페이지 조회 및 내 정보 수정 기능구현 && QueryDSL에 필요한 Repository 추가
* 결제 생성 기능 구현

- 주문 조회에 필요한 메서드를 위해 주문 도메인에 메서드 추가
- 에러 코드 도메인 구조별로 구역 분리
- 실행 안되는 에러 수정을 위해 RefundStatus 내용 추가
- 결제 생성 기능을 위한 메서드 추가, paymentUid는 일단 uuid를 이용하여 생성, 이후에 주문 도메인과 협의 후 tsid로 변경 가능성 있음

* 결제 생성 기능 구현
- 컨트롤러 추가
- 주문 생성 기능이 없어 테스트 불가능. 실행확인까지 했습니다.

* 결제 생성 기능 구현
- 로그인 유저 검증 추가
- 컨트롤러 PostMapping 추가
- PaymentCommandService DI 안되는 문제 final 추가로 해결

* Refactor: 팀원, AI 코드리뷰 반영하여 수정

* orderUid 검증 로직 추가

* Refactor: 결제 생성 시에도 order 조회 시 비관적 락 사용하여 조회. 검증 로직 추가

* Refactor :결제금액 및 배송비 0원 가능 정책 적용 및 검증 로직 추가

* Refactor : OrderUid 검증 패턴 변경 및 어노테이션 추가

* Feat: 결제 생성 로직 완성
- 에러 코드 수정
- 컨트롤러 입력값 검증(@validated) 에러 처리를 위해 GlobalExceptionHandler에 코드 추가
-

* Refactor : @pattern에서 길이 체크 해주기 때문에 @SiZe 제거, @pattern에 message 추가, 서비스 로직에서 orderUid 존재 검증 로직 제거

* Refactor : OrderStatus PAID -> COMPLETED로 변경

* Refactor : Payment Status에 EXPIRED 추가 및 사소한 변경
 - 본인이 주문한 주문 상세 조회
 - 스냅샷 테이블 데이터를 바탕으로 주문 당시 데이터 적용
 - 주문자 정보가 없는 데이터는 조회가 불가능하도록 설계
* Feat: 결제 생성 전 유저 정보 존재 여부 검증 로직 및 필요 메서드 추가

* trigger: AI 리뷰 재실행

* Chore: 에러코드 사소한 수정
* feat: 장바구니 상품 추가(생성)기능 구현

* chore: 간단한 오류 수정
* feat: 주문서 조회 API
* feat:상품 목록 조회, 상세 조회 개발, 더미데이터 3개 추가

* PR 수정 사항 반영

* feat: ProductRepositoryCustom -> ProductRepositoryCustomImpl로 수정 #28

* feat: 레포지토리 클래스 이름 수정 #28

* feat: 레포지토리 클래스 이름 수정(ProductRepositoryImpl -> ProductRepository) #28

* feat: 레포지토리 클래스 이름 수정(ProductRepositoryImpl -> ProductRepository) #28

* feat: 재고 관리 기록 서비스 로직 추가, 재고 차감/증가 서비스 로직 추가#35

* feat: pageable 설정 추가#35

* Update StockRepository.java

fix: Stock 엔티티 Integer -> Long

* fix: 컴플리트 해결

* fix: 코드 리뷰 수정

* fix: ProductCommandServiceImpl 에서 메소드 위 @transactional 삭제
CheatIsKey and others added 27 commits April 20, 2026 16:38
* feat: 주문서 고객정보 스냅샷 저장 및 주문 상태 변경
* feat: 재고 검증 로직 추가

* feat: 엔티티 재고 검증 로직 캡슐화 진행

* feat: 엔티티 재고 검증 로직 캡슐화 진행2
* Feat: 인기검색어의 검색어 카운트 API
 - 고객이 검색어를 입력하면 RedisZSet에 해당 검색어의 카운트를 +1 하는 핵심 로직
 - "search:rank:<현재 날짜>"를 인기 검색어의 Redis ZSet Key로 설정
 - "search:user:<현재 날짜>"를 날짜별 유저 검색의 Redis Set Key로 설정
 - 검색어 파싱을 공백(' ') 기준이 아닌, 전체로 판단해서 ZSet에 카운트
    - 앞뒤 공백 제거
    - 소문자로 변환
    - 중간 공백을 1개로 최소
    - 특수문자 제거
 - 같은 고객이 같은 날 똑같은 키워드로 검색해도 카운트가 중복처리 되지 않도록 구현
 - SADD를 사용해서 카운트 명령어를 원자적 실행 보장
 - 비회원 유저가 검색해도 집계되도록 수정 (IP 기반)
 - 회원 유저가 검색하면 똑같이 카운트 (userId 기반)
* chore: 장바구니 상품 추가-> 재고 확인 로직 수정
* feat: User도메인 마이페이지 조회 및 내 정보 수정 기능구현

* chore: AI 코드리뷰 내용 반영하여 수정

* feat: 비밀번호 수정 로직 분리

* chore: 사소한 변경

* feat: 장바구니 상품 추가(생성)기능 구현

* feat: 장바구니 전체 조회 기능 구현

* fix: ProductRepository -> ProductQueryService 변경
* Feat : 결제 확정 로직 완성
- OrderProductInfo 레코드 추가
- 주문 검증, 주문자와 로그인 유저 정보 검증, 결제 검증, 재고 차감, 주문 유저 스냅샷 정보 저장, 주문 상태 변경(completed->delivery completed)
- Order 도메인과 Product 도메인 코드 수정 있으니 확인 요망
-  재고관리 테이블에 변경 사항 저장하는 메서드 호출을 기존에 테스트 코드에서만 하셨어서 서비스 메서드에도 추가
- 위 과정에서 test 코드 오류 발생하여 주석처리 해뒀습니다. 변경사항에 맞게 test 코드 수정 및 기존 테스트 방법이 맞는 방법인지 검토 부탁드립니다.
* Feat: 서버 내부 테스트용 도커 컴포즈 파일 1차. redis 추가 및 수정 필요

* Feat: 서버 내부 테스트용 도커 컴포즈 파일 2차. redis 추가 및 k6 기본 테스트 파일 추가

* Chore : 변수 사용 추가

* Feat: .dockerignore 파일 추가

* Feat: Actuator 확인을 위해 SecurityConfig에 경로 오픈 및 applcation.yml 파일에도 엔드포인트 추가, k6 js 파일 등록, docker ignore 파일 추가
* Feat: 인기 검색어 Top5 조회
 - 1차 Redis에서 조회
 - 2차 Redis에 데이터가 없거나, 장애 발생 시 DB(PopularKeyword) 조회 fallback 수행
 - DB에 오늘 데이터가 없으면 어제 데이터로 재시도, 어제 데이터도 없으면 빈 리스트 반환
* feat: 장바구니 상품 수량 변경 기능 구현
* Feat: 1시간마다 Redis ZSet 데이터를 DB에 저장
 - Redis가 가지고 있는 데이터를 DB에 주기적으로 백업하는 Write-back 전략
 - Redis는 오늘 누적 전체가 담겨있어서 DB에 덮어쓰기
 - 스케쥴러를 활용해 정해진 시간마다 저장하도록 수행
* fix: rank 예약어 문제 해결
* feat: 장바구니 상품 단건(개별) (hard) 삭제 기능 구현

* chore: AI 코드리뷰 반영
* feat: 장바구니 비우기 기능 구현

* chore: 카멜케이스 표기법

* chore: develop 브랜치로 업로드 후 기능 테스트 완
* feat: 상품 5만건 더미데이터 자동 생성 로직추가
* Feat: 매일 자정에 인기 검색어 작업을 하는 스케쥴러
 - 오늘 최종 데이터 DB에 반영 - 마지막 Write-back
 - 오늘 Top5를 PopularKeyword DB에 스냅샷 저장
 - Redis 오늘 ZSet + 유저 기록 Set 초기화
 - 자정 직후 데이터가 없으니, Fallback Top5 생성 (어제 Top5 제외한 나머지 중 상위 5개)
 - 반드시 Write-back → 스냅샷 → 초기화 → Fallback 순서로 진행
* Feat: 매일 자정에 인기 검색어 작업을 하는 스케쥴러
 - 오늘 최종 데이터 DB에 반영 - 마지막 Write-back
 - 오늘 Top5를 PopularKeyword DB에 스냅샷 저장
 - Redis 오늘 ZSet + 유저 기록 Set 초기화
 - 자정 직후 데이터가 없으니, Fallback Top5 생성 (어제 Top5 제외한 나머지 중 상위 5개)
 - 반드시 Write-back → 스냅샷 → 초기화 → Fallback 순서로 진행

* Warm-up API 설계
* Feat: 동시성 문제 발생을 위한 기본 API 개발. ProductController에 ServiceImpl 참조하는 부분이 있길래 수정하였습니다

* Refactor: 코드리뷰 반영하여 수정하였습니다.
* Refactoring: 인기검색어 리팩토링

데이터 정합성
- SearchKeyword 엔티티에 UNIQUE(keyword, search_date) 제약 추가 — 분산 환경 동시 INSERT 방어
- PopularKeyword 엔티티에 UNIQUE(snapshot_date, keyword_rank) 제약 추가 — 중복 순위 저장 방어
- writeBack() 동시 INSERT 경쟁 방어 — DataIntegrityViolationException 캐치 후 update 전환

버그 수정
- midnightReset() 날짜 버그 수정 — writeBack(yesterday) 날짜 명시로 자정 직후 어제 데이터 유실 방지

분산 환경 대응
- @scheduled cron에 zone = "Asia/Seoul" 추가 — 서버 타임존 무관하게 한국 기준 실행 보장
- saveFallbackTop5() 멱등성 처리 — 중복 실행 시 오늘 데이터 이미 있으면 스킵
- snapshotTop5() 멱등성 처리 — 저장 전 기존 Fallback 삭제로 중복 방지

성능 개선
- doRecord() TTL 설정 로직 개선 — getExpire() 대신 hasKey()로 Redis 왕복 1회 감소

코드 품질
- RedisWarmUpRunner deprecated getConnection().ping() 제거 → hasKey() 방식으로 교체
- KeywordController 중복 import 정리
- 예외 로그 전체 e.getMessage() → e 로 변경하여 스택 트레이스 포함
- BaseEntity 적용
- activeFlag Integer 타입 -> Boolean 타입으로 하면 완료된 채팅방도 유니크 제약조건이 걸려서 여러 개 보유 불가
- updateLastMessageAt 방어를 적용해서 null, 미래 시각, 과거 시각을 차단
- content 길이 제약을 TEXT에서 VARCHAR로 바꿔서 인덱싱 효율 챙기기
* Config: WebSocket 및 STOMP 설정
 - 실시간 대화 기능을 구현하기 위한, WebSocket 및 STOMP 설정

* Refactor: CONNECT 시점에만 JWT 검증을 하도록 하고 SockJS 폴백ì� 가능하도록 입구(HTTP)는 열어준다. 그리고 STOMP 전용 Principal 객체를 사용한다.
* Feat: 인기 검색어 인메모리 캐시 적용
 - 첫 요청에는 Caffeine이 Top5가 캐시에 없음을 확인하고 v1 흐름과 동일하게 Redis → PopularKeyword DB 조회 및 결과를 Caffeine 메모리에 저장 후 반환
 - 60초(TTL) 이내 재요청하면 Caffeine이 별도 메서드 실행 없이 메모리에서 즉시 반환
 - 60초 경과 후 다시 DB 조회 및 반복

* Fix: 인기 검색어 인메모리 캐시 적용

- @CacheEvict를 추가해서 자정 스케쥴러 후 캐시 무효화 적용
- Redis TTL 경쟁 상태를 hasKey()가 아닌, 매번 expire() 덮어쓰기로 적용
- CacheConfig 캐시 이름 명시
- 캐시 통계 로그 추가
- 주석 일부 수정
* Feat: 실시간 채팅방 생성/종료/참가 메서드 구현

채팅방 생성
- 기존 활성화된 채팅방이 없는 경우, 채팅방을 생성
- 이미 있으면 기존 방 반환

채팅방 종료
- 고객이 상담 종료를 하는 경우, 관리자가 종료하는 경우
- WAITING 또는 IN_PROGRESS 상태가 COMPLETED로 전환
- active_flag를 null로 전환하면서 새로운 방 생성 가능하도록 설계

채팅방 참여 (관리자)
- 관리자는 모든 채팅방 참여 가능
- 일반 유저는 본인의 채팅방만 참여 가능
- WAITING 상태가 IN_PROGRESS로 전환

이외 요청, 응답 DTO 설계 및 메서드 틀 정의
* Feat: 실시간 채팅방 REST API 설계

채팅방 생성
- 컨트롤러 완성 및 연결

활성 채팅방 조회
- 활성된 방이 없으면 null 반환
- 프론트에서 null 체크 후 "상담원 연결" 버튼 표시

채팅방 단건 조회
- 고객은 본인 채팅방만 조회 가능
- 관리자는 모든 채팅방 조회 가능

채팅방 메시지 목록 조회
- 커서 페이징을 적용해서 무한 스크롤 기반 설계
- 스크롤 업 시 마지막 페이지 id를 cursorId로 전달

채팅방 종료
- 고객이 종료하거나, 일정 시간이 지나면 자동 종료

전체 채팅방 목록 조회 (관리자)
- 채팅방 상태를 파라미터로 받아 필터링

채팅방 참여 (관리자)
- WAITING -> IN_PROGRESS 상태 전환

모든 요청은 로그인이 필수로 요구되며, 관리자 기능은 권한을 추가로 검증한다.
* init:  프ë¡로젝트 초기 ì설정

* Feat: 채팅 WebSocket Redis Pub/Sub 연동 및 구조 개선

Redis Pub/Sub 연동 (다중 서버 대응)
- ChatRedisPublisher: 메시지 저장 후 Redis "chat:room:{roomId}" 채널에 발행
- ChatRedisSubscriber: Redis 채널 구독 후 WebSocket 구독자에게 브로드캐스트
- 기존 SimpleBroker 직접 브로드캐스트 → Redis 경유 방식으로 전환
- 서버 A에서 발행한 메시지를 서버 B의 구독자도 수신 가능

RedisConfig 수정
- 채팅 전용 chatRedisTemplate 빈 추가 (JSON 직렬화)
- JavaTimeModule 등록으로 LocalDateTime 직렬화 지원
- PatternTopic("chat:room:*")으로 모든 채팅방 채널을 단일 리스너로 처리
- RedisMessageListenerContainer 빈 등록

WebSocket 예외 처리 전역화
- ChatWebSocketController 내부 @MessageExceptionHandler 제거
- WebSocketExceptionHandler를 common/exception 패키지로 분리
- @ControllerAdvice + @MessageExceptionHandler로 전역 처리
- CustomException → WARN 레벨, 그 외 Exception → ERROR 레벨 분리

ChatErrorResponse DTO 분리
- ChatWebSocketController 내부 record 선언 제거
- domain/chat/dto/response/ChatErrorResponse.java로 독립 분리

SUBSCRIBE 인가 검증 추가
- StompChannelInterceptor에 SUBSCRIBE 시점 Principal 존재 여부 확인 추가
- 인증되지 않은 세션의 채널 구독 시도 차단
- SockJS 폴백과 무관한 레벨(STOMP 프레임 처리 단계)에서 수행

모든 상태 변경 메서드(closeChatRoom, joinChatRoom)에 비관적 락 적용
- findByIdForUpdate()로 SELECT FOR UPDATE 처리
- 고객 종료와 관리자 참여 동시 요청 시 데이터 정합성 보장
* feat: 인덱스 추가 로직 구현
* Feat: 동시성 문제 발생을 위한 기본 API 개발. ProductController에 ServiceImpl 참 조하는 부분이 있길래 수정하였습니다

* Test: EventOrder Without Lock Test - ticket 10 Users 100

* Refactor: 코드리뷰 반영하여 수정하였습니다.

* Test : 락 없는 버전 테스트 완료 및 주석 추가

* Refactor : 테스트 코드 리팩토링.
모든 테스트 코드 메서드 호출부 제외 통일
시간 측정 로직 추가
핵심 메서드를 제외한 부분은 시간 측정에서 제외

* Refactor : 동시성 테스트 코드 리팩토링
- Retry v1

* Feat: Redis Lock cherry pick 버전 가져오기. 락 없는 버전, 레디스 락 테스트 완료

* Test: Redis Retry Connection Pool 비교 테스트 완료

* Feat: Redis Lock을 AOP를 사용하여 구현하여 관심사 분리. 테스트 완료

* Feat: Redisson + AOP를 사용한 락 구현, 실제 주문이 들어왔을 때 호출은 아직 락 없는 버전을 호출하고 있습니다. 추후 최후 사용버전이 확정되면 EventOrderController에서 EventOrderFacade에 있는 메서드 중 골라서 호출하면 됩니다.

* Chore : 주석 해제

* chore: remove .DS_Store

* Chore-로깅 루트 수정

* chore: application-test.yml 파일 수정

* Remove secrets
* Feat: 비활성 채팅방 자동 종료 스케쥴러 구현

ChatInactivityScheduler 추가
- 1분 주기로 비활성 채팅방 감지 및 자동 종료 처리
- 기준 시간(inactivity-timeout-minutes) application.yml로 외부화
- 종료 대상: lastMessageAt 기준 설정 시간 경과 + WAITING/IN_PROGRESS 상태
- 개별 try-catch 적용으로 단일 방 처리 실패 시 나머지 방 계속 처리
- TODO: 분산락 적용 예정 (현재 비관적 락 + 상태 재확인으로 1차 방어)

autoCloseRoom() 추가
- 스케쥴러 전용 종료 메서드 분리 (closeChatRoom과 역할 분리)
- findByIdForUpdate() 비관적 락으로 동시 상태 변경 방어
- 이미 COMPLETED 상태면 스킵 (분산 환경 중복 실행 방어)
- 시스템 메시지 DB 저장 후 Redis Pub/Sub으로 클라이언트에 실시간 알림

findInactiveRooms() QueryDSL로 구현
- JPQL 전체 패키지명 방식 제거 → ChatRoomRepositoryCustomImpl에 QueryDSL로 통합
- chatRoom.chatRoomStatus.in(WAITING, IN_PROGRESS) + lastMessageAt.before(cutoff)
- 기존 ChatRoomRepositoryCustom 구조에 맞춰 일관성 유지
@CheatIsKey CheatIsKey closed this Apr 25, 2026
@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 3위 (리뷰 96건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

안녕하세요! CheatIsKey님, PR 테스트이자 대규모 프로젝트 세팅을 위한 첫 걸음을 내디디셨군요. Docker, CI/CD, K6 부하 테스트, 동시성 제어를 위한 다양한 Lock 전략까지, 실무에서 매우 중요한 인프라와 아키텍처 고민이 많이 엿보이는 훌륭한 밑바탕 코드입니다.

10년차 시니어 개발자 시점에서 코드를 꼼꼼히 리뷰하고, 과제 요구사항에 맞게 피드백을 드리겠습니다.

📋 과제 요구사항 준수 여부

  • 필수 기능 (동시성 제어 / 캐싱 / 검색): 현재 PR의 diff 상으로는 핵심 비즈니스 로직(서비스 계층, 컨트롤러)이 포함되지 않아 완전한 구현 여부를 확인하기 어렵습니다. 다만, build.gradle의 설정과 EventOrderFacade 클래스를 보면 요구사항을 충족하기 위한 멋진 기반을 다져두셨습니다.
  • 🔴 사용 금지 기술 사용 (Redisson): build.gradleredisson-spring-boot-starter:3.51.0이 추가되어 있으며, EventOrderFacade@RedissonLock이 사용되었습니다. 과제 요구사항상 필수 기능에서는 Redisson 사용이 금지되어 있으므로, 일반 주문 로직에서는 Lettuce 기반 Lock(@RedisLock)만 사용하시고 Redisson은 도전 기능 평가를 위해 남겨두시는 것이 좋습니다.

🟢 잘된 점

  1. 체계적인 인프라 구성: Docker Compose를 활용해 MySQL, Redis, App, K6 컨테이너를 명확하게 분리하고 depends_onhealthcheck를 설정하신 점이 훌륭합니다. 실무에서 컨테이너 기동 순서로 인해 발생하는 오류를 사전에 완벽하게 차단하셨습니다.
  2. 다양한 동시성 제어 전략 구현: EventOrderFacade에서 Lettuce 기반의 Fail-Fast, Retry, Blocking 전략을 모두 구현하시고 AOP로 추상화까지 해두신 점은 아주 인상적입니다. 각 전략의 트레이드오프를 이해하고 계신 것이 잘 드러납니다.
  3. 성능 테스트 대비 (K6): smoke, load, health 체크용 K6 스크립트를 미리 작성해 두어, 기능 개선 전후의 성능 지표를 정량적으로 비교할 수 있는 탄탄한 기반을 마련하셨습니다.

🔴 필수 수정

  1. .env 파일 강제 로드로 인한 운영 환경 보안 위험:

    • AlldayProjectCommerceApplication의 메인 메서드에서 Dotenv를 로드하여 시스템 프로퍼티에 강제로 주입하고 있습니다.
    • 문제점: AWS(EC2, ECS 등)나 Docker 환경에서 배포할 때, 환경 변수(SPRING_PROFILES_ACTIVE, DB_URL 등)를 외부 주입 방식으로 관리해야 하는데 이 코드가 충돌을 일으키거나 의도치 않게 로컬 설정 값을 우선적으로 가져가 버릴 위험이 있습니다.
    • 해결: 로컬 개발 편의를 위한 .env 로드는 Spring Boot의 프로파일 기능을 사용하거나, 최소한 로컬 환경에서만 동작하도록 제어해야 합니다.
  2. Facade 계층의 JPA 엔티티 직접 노출 (아키텍처 역전):

    • OrderPaymentFacade에서 jpa.basic.alldayprojectcommerce.domain.order.entity.Order (JPA 엔티티)를 직접 가져와 order.getUserId()로 검증하고 있습니다.
    • 문제점: Facade(Application 계층)는 도메인 계층의 엔티티 구현 기술(JPA)에 의존해서는 안 됩니다. 엔티티가 노출되면 향후 JPA가 아닌 다른 기술로 변경할 때 Facade 코드까지 수정해야 하는 유지보수 문제가 생깁니다.
    • 해결: OrderQueryService가 엔티티 대신 DTO(예: OrderResponse, OrderOwnerDto)를 반환하도록 캡슐화하세요.

🟡 권장 개선

  1. 대량의 더미 데이터 JPA saveAll() 시 성능 저하 (N+1 / 대량 INSERT 이슈):

    • DummyDataProduct에서 50,000건의 데이터를 리스트에 담아 productRepository.saveAll(products)로 저장하고 있습니다.
    • 문제점: Spring Data JPA의 기본 saveAll()은 내부적으로 영속성 컨텍스트에 50,000개의 엔티티를 모두 올립니다. 1차 캐시 메모리 부족, INSERT 쿼리 5만 번 실행 등으로 인해 애플리케이션 실행 시 심각한 성능 저하 및 OutOfMemory가 발생할 수 있습니다.
    • 해결 코드 (JDBC Batch Insert 권장):
      // build.gradle 의존성 추가
      + implementation 'org.springframework.boot:spring-boot-starter-jdbc'
      // DummyDataProduct.java 일부 수정
      @Component
      @RequiredArgsConstructor
      public class DummyDataProduct implements CommandLineRunner {
      
          private final JdbcTemplate jdbcTemplate; // JdbcTemplate 주입
      
          @Override
          @Transactional
          public void run(String... args) {
              if (productRepository.count() > 0) return;
      
              String sql = "INSERT INTO product (name, price, stock, description, status, category, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)";
              
              // 50,000건 Batch Insert
              jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                  @Override
                  public void setValues(PreparedStatement ps, int i) throws SQLException {
                      // products 리스트의 i번째 데이터를 ps에 바인딩
                  }
                  @Override
                  public int getBatchSize() {
                      return 50000;
                  }
              });
          }
      }
  2. Dockerfile 멀티 스테이지 빌드 미적용:

    • 현재 eclipse-temurin:21-jdk (JDK 포함, 약 500MB 이상)를 런타임 이미지로 사용하고 계십니다.
    • 개선 제안: 빌드 환경과 실행 환경을 분리하여 eclipse-temurin:21-jre (약 200MB 대)를 사용하면 컨테이너 크기를 절반 이하로 줄일 수 있고, 공격 표면(Attack Surface)도 줄일 수 있습니다.

💡 학습 포인트

  • Spring Boot 프로파일(Profile)과 외부 설정: .env 파일을 하드코딩된 방식이 아닌, spring.profiles.active=local 환경에서만 spring.config.import=optional:file:.env[.properties] 방식으로 주입하는 Spring Boot 공식 방법을 찾아보세요.
  • JPA Batch Insert와 하이버네이트 최적화: 대량의 데이터를 넣을 때 하이버네이트의 hibernate.jdbc.batch_size 설정과 ID 생성 전략(Identity vs Sequence)이 왜 영향을 미치는지 공부해 보시면 큰 도움이 됩니다.

🤔 생각해보기

현재 5만 건의 더미 데이터를 삽입할 때 CommandLineRunner를 사용해 애플리케이션이 켜질 때마다 체크 후 삽입하도록 구성하셨습니다. 만약 실제 실무 서비스 환경(운영 환경)에서도 이 애플리케이션이 켜

💬 리뷰에 대해 궁금한 점이 있나요? 코멘트에 @sparta 를 남겨보세요!
예: @sparta 이 코드에서 동시성 이슈가 발생할 수 있나요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

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.

5 participants