Skip to content

[volume-3] 도메인 모델링 및 구현 - 홍창모#80

Closed
HongChangMo wants to merge 28 commits intoLoopers-dev-lab:mainfrom
HongChangMo:round-03
Closed

[volume-3] 도메인 모델링 및 구현 - 홍창모#80
HongChangMo wants to merge 28 commits intoLoopers-dev-lab:mainfrom
HongChangMo:round-03

Conversation

@HongChangMo
Copy link
Copy Markdown
Collaborator

@HongChangMo HongChangMo commented Nov 14, 2025

📌 Summary

상품, 브랜드, 좋아요, 주문 기능의 도메인 모델 및 도메인 서비스 구현

💬 Review Points

@DisplayName("주문 생성 시 포인트가 부족하면 예외가 발생한다.")
@Test
void createOrder_withInsufficientPoint_throwException() {

    User mockUser = mock(User.class);
    when(mockUser.getPoint()).thenReturn(Money.of(10000));

    Product mockProduct = mock(Product.class);
    when(mockProduct.getPrice()).thenReturn(Money.of(25000));
    when(mockProduct.getStock()).thenReturn(Stock.of(100));

    Map<Product, Integer> productQuantities = new HashMap<>();
    productQuantities.put(mockProduct, 1); 

    CoreException exception = assertThrows(CoreException.class, () ->
        Order.createOrder(mockUser, productQuantities)
    );

    assertThat(exception.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
    assertThat(exception.getCustomMessage()).contains("포인트가 부족합니다");
}
  1. Order 단위 테스트 작성 중
    주문 생성 시 포인트가 부족하면 예외가 발생한다 에 관한 테스트 코드를 작성 중 궁금한 사항이 생겨 질문드립니다.
    단위 테스트는 단일 도메인에 대한 테스트를 진행해야 한다고 알고있습니다.
    하지만 해당 테스트를 진행하면서 Order 가 생성되지 않기 때문에 포인트가 부족 하더라도 Order 자체가 생성이 안되어야 하기때문에
    Order 댠위 테스트에 테스트를 진행하였는데, 이런 경우는 도메인 간 협력 로직 이기 때문에 도메인 서비스 테스트로 올려야하는 건지 궁금합니다

  2. 만약 위와같이 코드를 작성해도 괜찮다면,
    다른 도메인의 변경에 영향을 받지 않도록 해당 부분을 Mockito 를 활용해 테스트를 진행했는데,
    위의 코드의 경우 다른 도메인의 변경에 영향을 받지 않고 Order 도메인에 대한 예외가 발생하기 때문에 단위 테스트라고 생각해서 테스트 코드를 작성했는데, 단위 테스트로 볼 수 있는 테스트 코드인지 궁금합니다.

✅ Checklist

  • 상품 정보 객체는 브랜드 정보, 좋아요 수를 포함한다.

  • 상품의 정렬 조건(latest, price_asc, likes_desc) 을 고려한 조회 기능을 설계했다

  • 상품은 재고를 가지고 있고, 주문 시 차감할 수 있어야 한다

  • 재고는 감소만 가능하며 음수 방지는 도메인 레벨에서 처리된다

  • 좋아요는 유저와 상품 간의 관계로 별도 도메인으로 분리했다

  • 중복 좋아요 방지를 위한 멱등성 처리가 구현되었다

  • 상품의 좋아요 수는 상품 상세/목록 조회에서 함께 제공된다

  • 단위 테스트에서 좋아요 등록/취소/중복 방지 흐름을 검증했다

  • 주문은 여러 상품을 포함할 수 있으며, 각 상품의 수량을 명시한다

  • 주문 시 상품의 재고 차감, 유저 포인트 차감 등을 수행한다

  • 재고 부족, 포인트 부족 등 예외 흐름을 고려해 설계되었다

  • 단위 테스트에서 정상 주문 / 예외 주문 흐름을 모두 검증했다

  • 도메인 간 협력 로직은 Domain Service에 위치시켰다

  • 상품 상세 조회 시 Product + Brand 정보 조합은 도메인 서비스에서 처리했다

  • 복합 유스케이스는 Application Layer에 존재하고, 도메인 로직은 위임되었다

  • 도메인 서비스는 상태 없이, 도메인 객체의 협력 중심으로 설계되었다

  • 전체 프로젝트의 구성은 아래 아키텍처를 기반으로 구성되었다

    • Application → Domain ← Infrastructure
  • Application Layer는 도메인 객체를 조합해 흐름을 orchestration 했다

  • 핵심 비즈니스 로직은 Entity, VO, Domain Service 에 위치한다

  • Repository Interface는 Domain Layer 에 정의되고, 구현체는 Infra에 위치한다

  • 패키지는 계층 + 도메인 기준으로 구성되었다 (/domain/order, /application/like 등)

  • 테스트는 외부 의존성을 분리하고, Fake/Stub 등을 사용해 단위 테스트가 가능하게 구성되었다

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 사용자 회원가입 및 포인트 충전 시스템 추가
    • 상품 조회, 정렬(최신순, 가격, 인기도) 기능 추가
    • 상품 좋아요/취소 기능 추가
    • 주문 생성 및 상태 관리 기능 추가
    • 브랜드 관리 기능 추가
  • 문서

    • E-커머스 플랫폼 요구사항 및 시퀀스 다이어그램 문서 추가
    • 클래스 다이어그램 및 ER 다이어그램 문서 추가

- 회원 ID 값을 받아 회원 정보 조회 통합 테스트 작성
- 회원 ID 값을 받아 회원 포인트 정보 조회 통합, E2E 테스트 작성 완료
- 회원 ID 값과 충전할 포인트를 받아 회원 포인트 충전 단위, 통합, E2E 테스트 작성 완료
- 컨트롤러 코드 가독성 및 유지보수성 향상
- Gender 필드의 null 검증 버그 수정
- 메서드명 오타 수정
- 유저 시나리오를 기반으로한 요구사항 명세서 작성
- 요구사항 명세서 기반 Sequence Diagram 작성
- 요구사항 명세서 기반 Class Diagram 작성
- 01-requirements.md : 부분 실패 시나리오 명확화
- 02-sequence-diagrams.md : 외부 시스템 연동 시 오류 처리 시나리오 추가
- 04-erd.md : 불필요 컬럼 삭제
- Product 도메인 생성 및 단위 테스트 작성
- ProductLike 도메인 생성 및 User, Product 연관관계 설정
- Brand 도메인 생성
- 상품 등록 validation 로직 추가
- 상품 등록 unit test 코드 작성
- 도메인 validation 체크 기능 추가
- class diagram 수정
- 포인트 자료형 변경 Integer -> BigDecimal
- 자료형 변경에 따른 User 도메인, 통합, E2E 테스트 코드 수정
- 재고 입력값 검증 로직 구현
- 상품 목록 조회 기능 구현(sorting)
- SortType 관리를 위한 ProductSortType.java 생성
- 단위, 통합 테스트 코드 작성
- 주문 도메인 작성
- 주문 생성 단위 테스트 작성
- OrderStatus Enum class 활용, 주문 상태 관리
- 브랜드 사용/ 미사용 기능 구현
- 브랜드 생성 기능 구현
- 단위 테스트 작성
- 주문 생성 협력 관계 도메인 대상 로직 추가
- Product 도메인 : 상품 재고 감소 기능 추가
- User 도메인 : 사용자 포인트 사용 기능 추가
- 단위, 통합 테스트 작성 완료
- 재고(Stock) VO 객체로 분리
- 금액(Money) VO 객체로 분리
- VO 객체 분리로 인한 로직 수정 및 테스트 코드 수정
- 상품 좋아요 등록, 취소, 중복 방지 기능 구현
- 중복 좋아요 방지를 위한 멱등성 처리
- 주문 총 합계 금액 컬럼 불필요해보여 메서드로 수정
- BaseEntity 상속 제외 처리
- 클래스 다이어그램 제어 동작 상세화
- ERD 컬럼 설명 추가 및 개선
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 14, 2025

Caution

Review failed

The pull request is closed.

코드 리뷰 분석

Walkthrough

이 PR은 전자상거래 플랫폼의 핵심 도메인 모델과 API를 구현합니다. 사용자, 상품, 브랜드, 주문, 상품 좋아요 기능을 위한 도메인 엔티티, 저장소, 서비스, 애플리케이션 파사드 및 REST 컨트롤러를 추가합니다. 또한 가치 객체(Money, Stock), 테스트 및 설계 문서를 포함합니다.

Changes

응집도 / 파일(들) 변경 요약
가치 객체
apps/commerce-api/src/main/java/com/loopers/domain/Money.java, apps/commerce-api/src/main/java/com/loopers/domain/product/Stock.java
금액 및 재고 수량을 나타내는 불변 값 객체 도입. 산술 연산, 검증, 비교 메서드 제공
사용자 도메인
apps/commerce-api/src/main/java/com/loopers/domain/user/User.java, apps/commerce-api/src/main/java/com/loopers/domain/user/Gender.java, apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java, apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
User 엔티티, Gender 열거형, 저장소 인터페이스 및 비즈니스 로직 서비스 추가. 회원가입, 정보 조회, 포인트 충전 기능
상품 도메인
apps/commerce-api/src/main/java/com/loopers/domain/product/Product.java, apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java, apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java, apps/commerce-api/src/main/java/com/loopers/domain/product/ProductSortType.java
Product 엔티티, 정렬 타입 열거형, 저장소 및 서비스. 상품 등록, 조회, 정렬 기능
브랜드 도메인
apps/commerce-api/src/main/java/com/loopers/domain/brand/Brand.java
Brand 엔티티 도입. 활성화/비활성화 상태 관리
좋아요 기능
apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLike.java, apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLikeRepository.java, apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLikeService.java
ProductLike 엔티티, 저장소, 서비스. 멱등성 있는 좋아요 추가/취소
주문 도메인
apps/commerce-api/src/main/java/com/loopers/domain/order/Order.java, apps/commerce-api/src/main/java/com/loopers/domain/order/OrderStatus.java, apps/commerce-api/src/main/java/com/loopers/domain/order/OrderRepository.java, apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
Order 엔티티, 상태 열거형, 저장소, 서비스. 재고 차감 및 포인트 사용 검증 포함
주문 상품
apps/commerce-api/src/main/java/com/loopers/domain/orderproduct/OrderProduct.java
OrderProduct 엔티티. Order와 Product 간의 라인 아이템 표현
사용자 애플리케이션 계층
apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java, apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
도메인 User를 UserInfo DTO로 변환하는 파사드 및 레코드 도입
사용자 인프라
apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
JPA 저장소 및 도메인 저장소 구현체
상품 인프라
apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
JPA 저장소 및 도메인 저장소 구현체. 정렬 로직 포함
주문 인프라
apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
JPA 저장소 및 도메인 저장소 구현체
사용자 API
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java, apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java, apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1DTO.java
OpenAPI 스펙, REST 컨트롤러, 요청/응답 DTO. 회원가입, 정보 조회, 포인트 조회/충전 엔드포인트
예외 처리
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java
검증 실패 및 누락된 헤더에 대한 BAD_REQUEST 예외 핸들러 추가
테스트
apps/commerce-api/src/test/java/com/loopers/domain/.../\*Test.java, apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserV1ControllerE2ETest.java
도메인 엔티티, 서비스, 컨트롤러에 대한 단위 및 통합 테스트
문서
docs/week2/01-requirements.md, docs/week2/02-sequence-diagrams.md, docs/week2/03-class-diagram.md, docs/week2/04-erd.md, http/commerce-api/user-v1.http
요구사항, 시퀀스 다이어그램, 클래스 다이어그램, ERD, HTTP 요청 예제

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserV1Controller
    participant UserFacade
    participant UserService
    participant UserRepository
    participant Database

    Client->>UserV1Controller: POST /api/v1/users/new<br/>UserRequest(userId, email, birthdate, gender)
    activate UserV1Controller
    UserV1Controller->>UserFacade: accountUser(userId, email,<br/>birthdate, gender)
    activate UserFacade
    UserFacade->>UserService: accountUser(...)
    activate UserService
    
    rect rgb(200, 220, 255)
    Note over UserService: 중복 체크
    UserService->>UserRepository: existsByUserId(userId)
    activate UserRepository
    UserRepository->>Database: SELECT * FROM users<br/>WHERE user_id = ?
    Database-->>UserRepository: 결과
    deactivate UserRepository
    end
    
    alt 존재하는 경우
        UserService-->>UserService: throw CoreException<br/>(BAD_REQUEST)
    else 존재하지 않는 경우
        UserService->>UserService: User.createUser(...)
        UserService->>UserRepository: save(user)
        activate UserRepository
        UserRepository->>Database: INSERT INTO users ...
        Database-->>UserRepository: user entity
        deactivate UserRepository
        UserService-->>UserFacade: User
    end
    
    deactivate UserService
    UserFacade->>UserFacade: UserInfo.from(user)
    UserFacade-->>UserV1Controller: UserInfo
    deactivate UserFacade
    UserV1Controller->>UserV1Controller: UserV1DTO.UserResponse.from(userInfo)
    UserV1Controller-->>Client: 200 OK<br/>ApiResponse<UserResponse>
    deactivate UserV1Controller
Loading
sequenceDiagram
    participant Client
    participant OrderService as OrderService<br/>(Transactional)
    participant OrderRepository
    participant ProductRepository
    participant UserRepository
    participant Database

    Client->>OrderService: createOrder(user,<br/>productQuantities)
    activate OrderService
    
    rect rgb(200, 220, 255)
    Note over OrderService: Order 생성 및 검증
    OrderService->>OrderService: Order.createOrder(user,<br/>productQuantities)
    Note over OrderService: - 재고 검증<br/>- 포인트 검증<br/>- 총액 계산
    end
    
    rect rgb(200, 255, 220)
    Note over OrderService: 각 상품별 재고 차감
    loop for each product in productQuantities
        OrderService->>ProductRepository: findProduct(...)
        activate ProductRepository
        ProductRepository->>Database: SELECT...
        Database-->>ProductRepository: product
        deactivate ProductRepository
        OrderService->>OrderService: product.decreaseStock(qty)
    end
    end
    
    rect rgb(255, 220, 220)
    Note over OrderService: 사용자 포인트 사용
    OrderService->>UserRepository: findUser(user.getId())
    activate UserRepository
    UserRepository->>Database: SELECT...
    Database-->>UserRepository: user
    deactivate UserRepository
    OrderService->>OrderService: user.usePoint(order.totalPrice)
    end
    
    rect rgb(220, 240, 255)
    Note over OrderService: Order 저장
    OrderService->>OrderRepository: save(order)
    activate OrderRepository
    OrderRepository->>Database: INSERT INTO orders...
    Database-->>OrderRepository: order
    deactivate OrderRepository
    end
    
    OrderService-->>Client: Order 생성 완료
    deactivate OrderService
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

주의가 필요한 영역:

  • 도메인 로직 검증: Order.createOrder(), User.chargePoint()/usePoint(), Product.decreaseStock() 등에서 비즈니스 규칙(재고 부족, 포인트 부족, 중복 회원가입 등) 검증 로직이 정확하게 구현되었는지 확인 필요
  • 트랜잭션 경계: OrderService.createOrder()@Transactional로 감싸져 있으며, 여러 엔티티(Product, User, Order)의 상태 변경이 원자성 있게 처리되는지 확인
  • 임베드 값 객체: MoneyStock의 불변성과 연산 오버로드(add, subtract, multiply) 로직 검증
  • N+1 쿼리: ProductJpaRepository.findByIdWithBrand()의 LEFT JOIN FETCH 쿼리가 의도대로 작동하는지 확인
  • 좋아요 멱등성: ProductLikeService.addLike()가 중복 요청 시 멱등하게 동작하는지 확인
  • API 응답: UserV1Controller 및 DTO 변환 로직이 일관되게 적용되었는지 확인
  • 테스트 커버리지: 주요 서비스, 도메인 로직, 컨트롤러 E2E 테스트가 포함되었으나, 예외 시나리오 및 엣지 케이스 커버리지 확인 필요

Possibly related PRs

  • PR #59: 동일한 User, Gender, UserRepository, UserService 도메인 아티팩트를 추가하며 포인트 관련 서비스/엔티티를 함께 구현
  • PR #31: 동일한 UserFacade와 UserInfo 레코드를 com.loopers.application.user에 추가하며, UserService와 PointService를 연결
  • PR #26: 회원가입, 사용자 정보 조회, 포인트 조회/충전 등 동일한 사용자 흐름을 구현하고 User, UserService/Facade, UserRepository/구현체, UserV1Controller/DTO를 추가

Poem

🐰 새로운 상품들이 담길 쇼핑백을 짜내고,
포인트는 쏙쏙 빠지고 들어오며,
주문이 우르르 쌓여가는 이 날,
토끼는 웃음 지으며 코드를 다듬네!
브랜드부터 좋아요까지, 모두 모여
전자상거래의 마법이 시작되노라! ✨🛒

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9ae7ad and 5b89abc.

📒 Files selected for processing (47)
  • apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/Money.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/Brand.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLike.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLikeRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/like/ProductLikeService.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/order/Order.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderStatus.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/orderproduct/OrderProduct.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/product/Product.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductSortType.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/product/Stock.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/user/Gender.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/user/User.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java (3 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1DTO.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/like/ProductLikeServiceTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/like/ProductLikeTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/orderproduct/OrderProductTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductServiceTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java (1 hunks)
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserV1ControllerE2ETest.java (1 hunks)
  • docs/week2/01-requirements.md (1 hunks)
  • docs/week2/02-sequence-diagrams.md (1 hunks)
  • docs/week2/03-class-diagram.md (1 hunks)
  • docs/week2/04-erd.md (1 hunks)
  • http/commerce-api/user-v1.http (1 hunks)

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.

1 participant