Skip to content

[Volume 3] 도메인 모델링 및 구현 - 양권모#123

Open
Praesentia-YKM wants to merge 23 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Praesentia-YKM:volume-3
Open

[Volume 3] 도메인 모델링 및 구현 - 양권모#123
Praesentia-YKM wants to merge 23 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Praesentia-YKM:volume-3

Conversation

@Praesentia-YKM
Copy link

📌 Summary

  • 배경: Week3에서 Brand, Product, Stock 도메인 CRUD가 완료되었으나, 이커머스의 핵심인 '사용자 행동(좋아요, 주문)'과 공통 인증/인가 체계가 부재함.
  • 목표: Like/Order 도메인 구현 및 커스텀 어노테이션 기반 인증 체계 구축을 통해 총 21개 API 완성.
  • 결과: Customer 9개 + Admin 12개 API 구현 완료. 전 구간 TDD 적용 (Unit / Integration / E2E).

🧭 Context & Decision

문제 정의

  • 현상: 인증 로직이 @RequestHeader로 컨트롤러마다 산재하여 중복 발생 및 변경 취약성 존재.
  • 성공 기준: 21개 API 전체 구현 및 @LoginMember, @AdminUser를 통한 인증 로직 일원화.

주요 의사결정 및 트레이드오프

  1. **좋아요 삭제 전략: Soft Delete **
    • 이유: 물리 삭제는 단순하지만, 상품 복구 시 연관 데이터(좋아요) 유실 위험이 있음. BaseEntity 패턴과의 일관성 유지.
  2. **좋아요 수(likeCount) 동기화: Application Layer 직접 호출 **
    • 이유: DDD 이벤트 방식이 이상적이나 현재 규모에선 인프라 복잡도 증가. LikeFacade에서 직접 처리하여 단순성 확보.
  3. **주문 재고 차감 시점: 주문 생성 시 즉시 차감 **
    • 이유: 단일 트랜잭션 내에서 All-or-Nothing 보장. 향후 결제 도입 시 Saga 패턴 등으로 확장 고려.
  4. **Brand-Product Aggregate 경계: 별도 Aggregate + ID 참조 **
    • 이유: 동일 Aggregate 구성 시 상품 수 증가에 따른 성능 저하 우려. BrandFacade에서 연쇄 Soft Delete로 정합성 유지.

🏗️ Design Overview

레이어 컴포넌트 책임
Auth @LoginMember, @AdminUser ArgumentResolver를 통한 인증 객체 주입 및 중복 로직 제거
Like LikeFacade, LikeModel 멱등성 보장, likeCount 동기화, Soft Delete 처리
Order OrderFacade, OrderModel 재고 차감 및 주문/아이템 스냅샷 저장 (Atomic 처리)
Brand BrandFacade 브랜드 삭제 시 소속 상품들에 대한 연쇄 Soft Delete 관리
Product ProductFacade 상품 + Stock 동시 생성 및 권한별 조회 필터링

🔁 Flow Diagram

1. 주문 생성 (Order Flow)

sequenceDiagram
      autonumber
      participant Client as Customer
      participant Ctrl as OrderV1Controller
      participant Facade as OrderFacade
      participant PS as ProductService
      participant SS as StockService
      participant OS as OrderService

      Client->>Ctrl: POST /api/v1/orders (@LoginMember)
      Ctrl->>Facade: placeOrder(userId, commands)

      rect rgb(230, 240, 255)
          Note right of Facade: @Transactional
          loop 각 주문 상품
              Facade->>PS: getProduct(productId)
              Note over PS: 삭제된 상품 → NOT_FOUND
              Facade->>SS: getByProductId → stock.decrease()
              Note over SS: 재고 부족 → BAD_REQUEST (전체 롤백)
          end
          Facade->>Facade: 총액 서버 계산 (Money.add/multiply)
          Facade->>OS: Order + OrderItem 저장 (스냅샷)
      end

      OS-->>Facade: OrderResult
      Facade-->>Ctrl: OrderResult
      Ctrl-->>Client: 200 OK (주문 정보)

Loading

2. 좋아요 토글 (Like Flow)

sequenceDiagram
      autonumber
      participant Client as Customer
      participant Facade as LikeFacade
      participant PS as ProductService
      participant LS as LikeService

      Client->>Facade: like(userId, productId)
      rect rgb(230, 240, 255)
          Note right of Facade: @Transactional
          Facade->>PS: getProduct (삭제 확인)
          Facade->>LS: findByUserIdAndProductId

          alt 좋아요 없음
              Facade->>LS: save(new LikeModel)
              Facade->>PS: product.incrementLikeCount()
          else 삭제된 좋아요 존재
              Facade->>LS: restore()
              Facade->>PS: product.incrementLikeCount()
          else 이미 활성 좋아요
              Note over Facade: 무시 (멱등성)
          end
      end
      Facade-->>Client: 200 OK

Loading

3. 브랜드 삭제 연쇄 (Brand Cascade Flow)

sequenceDiagram
      autonumber
      participant Admin
      participant Facade as BrandFacade
      participant BS as BrandService
      participant PS as ProductService

      Admin->>Facade: delete(brandId) (@AdminUser)
      rect rgb(230, 240, 255)
          Note right of Facade: @Transactional
          Facade->>BS: delete(brandId) → soft delete
          Facade->>PS: deleteAllByBrandId(brandId)
          loop 소속 상품 전체
              PS->>PS: product.delete() → soft delete
          end
      end
      Facade-->>Admin: 200 OK

Loading
  1. 상품 목록 조회 — 고객 vs 어드민 (Product List Flow)
sequenceDiagram
     autonumber
     participant C as Customer
     participant A as Admin
     participant F as ProductFacade
     participant PS as ProductService
     participant SS as StockService

     rect rgb(232, 245, 233)
         Note over C,SS: Customer 상품 목록 조회 (상태 중심)
         C->>F: GET /api/v1/products?sort=likes_desc
         F->>PS: 상품 목록 조회 (삭제 제외, 정렬)
         F->>SS: 재고 정보 조합
         Note over F: 재고 상태 변환 로직<br/>>10: IN_STOCK<br/>1~10: LOW_STOCK<br/>0: OUT_OF_STOCK
         F-->>C: 200 OK (상품 목록 + 재고 상태)
     end

     rect rgb(227, 242, 253)
         Note over A,SS: Admin 상품 목록 조회 (수량 중심)
         A->>F: GET /api-admin/v1/products (@AdminUser)
         F->>PS: 상품 목록 조회 (삭제 포함 가능)
         F->>SS: 재고 정보 조합
         Note over F: 정확한 재고 수량 노출
         F-->>A: 200 OK (상품 목록 + 실제 재고 수량)
     end
Loading

hanyoung-kurly and others added 23 commits February 2, 2026 01:26
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- spring-security-crypto 의존성 추가 (BCryptPasswordEncoder)
- PasswordEncoderConfig 빈 등록
- ErrorType에 UNAUTHORIZED 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 4개 VO 구현 (LoginId, Password, MemberName, Email)
- MemberModel 엔티티 (@Embedded VO, matchesPassword 행위 메서드)
- MemberRepository 인터페이스 및 JPA 구현
- ErrorType 도메인 에러 코드 추가 (10개)
- 단위 테스트: VO 검증 + MemberModel + Repository 통합테스트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberSignupService (중복 체크, 비밀번호 암호화, 저장)
- 단위 테스트: Mock을 활용한 동작 검증
- 통합 테스트: 실제 DB 연동 검증

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberAuthService (loginId/password 검증, 회원 조회)
- 단위 테스트: Mock을 활용한 동작 검증
- 통합 테스트: 실제 DB 연동 검증

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberPasswordService (현재 비밀번호 검증, 새 비밀번호 암호화 저장)
- 단위 테스트: Mock을 활용한 동작 검증
- 통합 테스트: 실제 DB 연동 검증

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberFacade (signup, getMyInfo, changePassword)
- MemberInfo 응답 DTO (이름 마스킹 포함)
- MemberV1Controller (POST /members, GET /me, PATCH /me/password)
- MemberV1Dto (SignupRequest, MemberResponse, ChangePasswordRequest)
- E2E 테스트: MemberV1ApiE2ETest
- MemberFacadeTest 단위 테스트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Example/Core 테스트 DisplayName 자연스럽게 개선
- TEST-README.md 테스트 체크리스트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix : 예제 테스트 코드 오류 해결을 위한 testcontainers 버전 업
- 기능요구정의서
- 유비쿼터스 언어 정의서
- 시퀀스 다이어그램
- 클래스 다이어그램
- erd
- BrandName VO: 빈값/null/공백 검증, equals/hashCode
- BrandModel Entity: BaseEntity 상속, soft delete
- BrandService: 등록(중복체크), 조회, 수정(중복체크), 삭제, 목록
- BrandRepository 인터페이스 + JPA 구현체
- Customer API: GET /api/v1/brands/{brandId}
- Admin API: POST/GET/PUT/DELETE /api-admin/v1/brands
- 테스트: BrandNameTest, BrandModelTest, BrandServiceTest,
  BrandServiceIntegrationTest, BrandV1ApiE2ETest
- HTTP 테스트 파일: brand-v1.http

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Money VO, ProductModel, StockModel 엔티티 구현
- ProductService, StockService 도메인 서비스 구현
- Customer/Admin API 컨트롤러 및 DTO 구현
- 브랜드 삭제 시 소속 상품 연쇄 soft delete 구현
- Unit/Integration/E2E 테스트 작성 (51개 Unit 테스트 통과)
- HTTP 수동 테스트 파일 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LikeModel 엔티티 (userId, productId, soft delete)
- LikeService, LikeRepository, LikeRepositoryImpl
- LikeFacade: 좋아요 등록/취소 멱등성 처리 + likeCount 비정규화
- LikeV1Controller: POST/DELETE /api/v1/products/{productId}/likes, GET /api/v1/users/{userId}/likes
- ProductModel: incrementLikeCount/decrementLikeCount 추가 (음수 방지)
- ApiControllerAdvice: MissingRequestHeaderException 핸들러 추가
- 삭제된 상품 좋아요 목록 제외 (JPQL 서브쿼리)
- Unit 6 + Integration 9 + E2E 7 테스트 전체 통과 (총 222개/0실패)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- @LoginMember: ArgumentResolver로 X-Loopers-LoginId/Pw 헤더 인증 후 MemberModel 주입
- @adminuser: X-Loopers-Ldap 헤더 검증 후 AdminInfo 주입
- WebMvcConfig에 ArgumentResolver 등록
- MemberFacade에서 인증 책임 제거 (ArgumentResolver로 이동)
- 전체 컨트롤러 리팩토링: 반복적인 인증 보일러플레이트 제거
- Facade 레이어 분리: BrandFacade, ProductFacade, LikeWithProduct 추가
- E2E 테스트에 어드민 LDAP 헤더 적용

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OrderModel/OrderItemModel 엔티티, OrderStatus 상태 전이
- OrderService, OrderFacade (주문 생성 시 재고 차감 연동)
- OrderV1Controller (고객), OrderAdminV1Controller (어드민)
- 단위/통합/E2E 테스트 포함 (239 tests, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
auth-annotation(인증인가+DDD리팩토링) + order(주문CRUD) 병합
- 충돌 해결: BrandFacade, ProductService, ApiControllerAdvice
- OrderFacadeIntegrationTest: productFacade.register() 사용으로 변경
- OrderV1ApiE2ETest: admin 헤더 추가, /api/v1/users 경로 반영
- ProductServiceTest: 제거된 getBrandNamesByIds 테스트 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

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.

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

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.

3 participants