-
Notifications
You must be signed in to change notification settings - Fork 34
[2주차] 설계 문서 제출 - 이민규 #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: leeminkyu-kr96
Are you sure you want to change the base?
[2주차] 설계 문서 제출 - 이민규 #57
Conversation
Walkthrough사용자 및 포인트 관리 기능을 위한 전체 스택 구현이 추가되었습니다. 도메인 모델, 저장소, 서비스, 파사드, REST API 컨트롤러, DTO, 그리고 포괄적인 단위 및 통합 테스트가 포함됩니다. 설계 문서로는 요구사항, 시퀀스 다이어그램, 클래스 다이어그램, ERD가 제공됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController as UserV1Controller
participant UserFacade
participant UserService
participant UserRepository
participant UserDB as User DB
participant PointFacade
participant PointService
participant PointRepository
participant PointDB as Point DB
rect rgb(200, 220, 240)
Note over Client,UserController: 사용자 가입 (Signup)
Client->>UserController: POST /api/v1/users/signup<br/>(userId, email, gender, birthDate)
UserController->>UserFacade: signup(userId, email, birthDate)
UserFacade->>UserService: signUp(UserModel)
UserService->>UserRepository: find(userId)
UserRepository->>UserDB: SELECT by userId
alt User Exists
UserService->>UserService: throw CONFLICT
else User Not Exists
UserService->>UserRepository: save(UserModel)
UserRepository->>UserDB: INSERT
UserDB-->>UserRepository: UserModel
UserRepository-->>UserService: UserModel
end
UserService-->>UserFacade: UserModel
UserFacade-->>UserController: UserInfo
UserController-->>Client: 201 Created<br/>UserResponse
end
rect rgb(220, 240, 200)
Note over Client,PointController: 포인트 충전 (Charge Points)
Client->>UserController: POST /api/v1/points/charge<br/>X-USER-ID: userId<br/>(amount)
UserController->>PointFacade: chargePoint(userId, amount)
PointFacade->>UserService: getUser(userId)
alt User Not Found
UserService->>PointFacade: null
PointFacade->>PointFacade: throw NOT_FOUND
else User Found
UserService-->>PointFacade: UserModel
PointFacade->>PointService: charge(PointModel)
PointService->>UserRepository: find(userId)
UserRepository-->>PointService: UserModel
rect rgb(255, 250, 200)
Note over PointService,PointDB: Transaction
PointService->>PointRepository: findPoint(UserModel)
PointRepository->>PointDB: SELECT by user
alt Point Exists
PointDB-->>PointRepository: PointModel
PointRepository-->>PointService: Optional(PointModel)
PointService->>PointService: charge amount
PointService->>PointRepository: save(PointModel)
else Point Not Exists
PointRepository-->>PointService: Optional.empty()
PointService->>PointService: create new PointModel
PointService->>PointRepository: save(PointModel)
end
PointRepository->>PointDB: INSERT/UPDATE
PointDB-->>PointRepository: PointModel
end
PointRepository-->>PointService: PointModel
PointService-->>PointFacade: void
PointFacade->>PointRepository: findPoint(new PointModel(user, 0))
PointRepository->>PointDB: SELECT by user
PointDB-->>PointRepository: PointModel
PointRepository-->>PointFacade: PointModel
PointFacade-->>UserController: PointInfo
UserController-->>Client: 200 OK<br/>PointResponse
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 주의가 필요한 영역:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Nitpick comments (18)
.docs/design/04-erd.md (1)
1-48: ERD 구조는 합리적이나 네이밍 일관성 주의 필요.ERD의 전체 구조와 관계 정의는 도메인 모델을 잘 반영합니다. 다만 클래스 다이어그램과의 네이밍 일관성을 확인하세요:
- Products 테이블의
stock_quantityvs 클래스 다이어그램의quantity- Orders 테이블의
created_atvs 클래스 다이어그램의orderDate또한 데이터 타입 관점에서
int타입 사용(price, point 등)이 대규모 거래량을 감당할 수 있는지 검토하는 것을 권장합니다(오버플로우 위험)..docs/design/02-sequence-diagrams.md (1)
30-64: 주문 생성 시퀀스에서 예외 흐름 상세화 권장.주문 생성 시퀀스 다이어그램의 TransactionBlock은 적절하게 설계되었습니다. 다만 다음 예외 상황에 대한 흐름을 명시적으로 추가하면 더욱 명확해질 것입니다:
- 재고 부족 시 흐름 (라인 55에서 실패한 경우)
- 포인트 부족 시 흐름 (라인 56에서 실패한 경우)
- 트랜잭션 롤백 시 응답 방식
현재는 성공 경로만 명시되어 있으므로, 요구사항 명세(01-requirements.md)의 Exception Flow들을 시퀀스 다이어그램에도 반영하면 설계의 완전성이 향상될 것입니다.
.docs/design/01-requirements.md (1)
1-137: 설계 문서 전체: 구조와 내용 종합 평가.요구사항 명세서의 전체 구조는 우수하며, 사용자 시나리오 기반의 기능 정의와 Main/Alternate/Exception Flow 분류가 체계적입니다. 특히 Exception Flow가 명확하게 정의된 점은 좋습니다(재고 부족, 포인트 부족, 비회원 등).
다음 항목들을 최종 검토하세요:
네이밍 일관성: ERD, 클래스 다이어그램, 시퀀스 다이어그램, 요구사항 간 필드명 통일
quantityvsstock_quantityorderDatevscreated_at설계 상세도: 시퀀스 다이어그램에 Exception Flow와 트랜잭션 롤백 시나리오 명시화
데이터 타입 검토:
int타입의 가격/포인트 필드에 대한 overflow 고려제출된 설계 문서들은 전체적으로 일관성 있게 작성되었으며, 요구사항을 반영한 도메인 모델링이 적절합니다.
apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java (1)
6-7:find메서드를findByUserId로 명확히 해주세요.
도메인 인터페이스에서find(String userId)라는 이름은 조회 기준이 무엇인지 불분명해 오용 가능성이 큽니다.findByUserId처럼 의도를 드러내도록 이름을 조정하면 가독성과 유지보수성이 좋아집니다.필요한 경우 아래처럼 정리해 주세요:
- Optional<UserModel> find(String userId); + Optional<UserModel> findByUserId(String userId);apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java (1)
6-12: 애플리케이션 계층에서 도메인 엔티티 노출을 피하는 편이 좋습니다.
현재PointInfo가UserModel을 그대로 담고 있어 애플리케이션 계층에서 도메인 엔티티가 외부로 새는 구조입니다.UserInfo로 변환해 전달하면 계층 간 결합도가 낮아지고 이후 API DTO 매핑도 더 명확해집니다.아래와 같이 조정해 보세요:
-import com.loopers.domain.user.UserModel; +import com.loopers.application.user.UserInfo; -public record PointInfo(Long id, UserModel user, int point) { +public record PointInfo(Long id, UserInfo user, int point) { public static PointInfo from(PointModel model) { - return new PointInfo(model.getId(), model.getUser(), model.getPoint()); + return new PointInfo( + model.getId(), + UserInfo.from(model.getUser()), + model.getPoint() + ); }apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java (1)
17-26: 이메일과 생년월일에 대한 형식 검증 추가를 고려하세요.현재
@NotBlank검증만 있어서 빈 값은 방지하지만, 잘못된 형식의 데이터는 허용됩니다. 이메일 형식과 날짜 형식에 대한 추가 검증을 고려해보세요.다음과 같이 검증을 추가할 수 있습니다:
public record SignupRequest( @NotBlank(message = "userId는 필수입니다.") String userId, - @NotBlank(message = "email은 필수입니다.") + @NotBlank(message = "email은 필수입니다.") + @Email(message = "유효한 이메일 형식이 아닙니다.") String email, @NotBlank(message = "gender는 필수입니다.") String gender, - @NotBlank(message = "birthDate는 필수입니다.") + @NotBlank(message = "birthDate는 필수입니다.") + @Pattern(regexp = "\\d{4}-\\d{2}-\\d{2}", message = "생년월일은 yyyy-MM-dd 형식이어야 합니다.") String birthDate ) {}
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Pattern;apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java (1)
7-10: LGTM - 메서드 네이밍 개선 제안.인터페이스 정의는 깔끔합니다. 다만
findPoint(UserModel user)메서드명을findByUser(UserModel user)로 변경하면 JPA 컨벤션과 더 일관성을 가질 수 있습니다. PointJpaRepository에서 이미findByUser를 사용하고 있으므로 도메인 레이어에서도 동일한 네이밍을 사용하는 것이 좋습니다.apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Dto.java (1)
16-19: primitive int 대신 Integer 래퍼 클래스 사용을 고려하세요.primitive int는 JSON 요청에서 amount 필드가 누락되면 기본값 0으로 설정됩니다. 이는
@Min(1)검증을 우회하지는 못하지만, 필드 누락과 명시적인 0 값을 구분할 수 없게 만듭니다. Integer 래퍼 클래스를 사용하면@NotNull검증을 추가하여 더 명확한 에러 메시지를 제공할 수 있습니다.다음과 같이 수정할 수 있습니다:
public record ChargeRequest( + @NotNull(message = "포인트 금액은 필수입니다.") - @Min(value = 1, message = "포인트는 1 이상이어야 합니다.") + @Min(value = 1, message = "포인트는 1 이상이어야 합니다.") - int amount + Integer amount ) {}apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java (1)
48-63: PointService.findPoint API 사용이 혼란스럽습니다.Line 56에서
pointService.findPoint(pointModel)을 호출할 때 amount가 10인 PointModel을 전달하지만, 실제로는 user 정보만 사용됩니다. PointService.findPoint는 PointModel을 받지만 내부적으로는 user만 사용하므로 혼란을 줄 수 있습니다.PointService.findPoint 메서드 시그니처를 다음과 같이 변경하는 것을 고려하세요:
// PointService.java public PointModel findPoint(UserModel user) { var foundUser = userRepository.find(user.getUserId()); if (foundUser.isEmpty()) { return null; } return pointRepository.findPoint(foundUser.get()).orElse(null); }그러면 테스트 코드도 더 명확해집니다:
PointModel result = pointService.findPoint(user);apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (2)
19-27: 메서드 시그니처와 중복 조회를 개선하세요.
findPoint메서드가PointModel을 파라미터로 받지만 실제로는user정보만 사용하고 있습니다. 또한, 이미PointModel에 포함된 사용자를 다시UserRepository로 조회하고 있어 중복 조회가 발생합니다.다음과 같이 개선할 수 있습니다:
- @Transactional(readOnly = true) - public PointModel findPoint(PointModel point) { - UserModel requestUser = point.getUser(); - var foundUser = userRepository.find(requestUser.getUserId()); - if (foundUser.isEmpty()) { - return null; - } - return pointRepository.findPoint(foundUser.get()).orElse(null); - } + @Transactional(readOnly = true) + public PointModel findPoint(UserModel user) { + return pointRepository.findPoint(user).orElse(null); + }
29-42: 일관성 없는 에러 처리를 개선하세요.
charge메서드는 사용자가 없을 때 예외를 던지지만,findPoint메서드는 null을 반환합니다. 또한charge메서드도PointModel전체를 받지만 실제로는 user와 point 금액만 필요합니다. PointFacade에서 이미 사용자 존재 여부를 확인하므로 여기서 다시 조회하는 것도 중복입니다.다음과 같이 개선할 수 있습니다:
@Transactional - public void charge(PointModel point) { - UserModel user = point.getUser(); - var foundUser = userRepository.find(user.getUserId()) - .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "유저가 존재하지 않습니다.")); - - var existing = pointRepository.findPoint(foundUser); + public PointModel charge(UserModel user, int amount) { + var existing = pointRepository.findPoint(user); if (existing.isPresent()) { - existing.get().charge(point.getPoint()); - pointRepository.save(existing.get()); - return; + PointModel existingPoint = existing.get(); + existingPoint.charge(amount); + return pointRepository.save(existingPoint); } - pointRepository.save(new PointModel(foundUser, point.getPoint())); + return pointRepository.save(new PointModel(user, amount)); }apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1)
17-20: 일관성 없는 에러 처리 방식을 개선하세요.
getUser메서드는 사용자를 찾지 못하면 null을 반환하지만,signUp메서드는 중복 시 예외를 던집니다. 이로 인해 Facade 레이어에서 매번 null 체크를 해야 합니다 (PointFacade.java 19-22줄, 34-37줄, UserFacade.java 18-21줄 참조).다음과 같이 Optional을 반환하도록 개선할 수 있습니다:
@Transactional(readOnly = true) - public UserModel getUser(String userId) { - return userRepository.find(userId).orElse(null); + public Optional<UserModel> getUser(String userId) { + return userRepository.find(userId); }apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java (1)
91-112: 테스트 설명이 구체적이지 않습니다.테스트 이름이 "회원 가입 시에 성별이 없을 경우"이지만 실제로는 gender 필드를 null로 설정하여 @notblank 검증을 테스트하고 있습니다. 테스트가 검증하는 내용을 더 명확하게 표현하면 좋겠습니다.
- @DisplayName("회원 가입 시에 성별이 없을 경우, 400 Bad Request 응답을 반환한다.") + @DisplayName("회원 가입 시에 필수 필드(gender)가 null일 경우, 400 Bad Request 응답을 반환한다.")apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (2)
20-23: 스텁 구현이 있습니다.
findById메서드가 항상Optional.empty()를 반환하는 스텁 구현으로 남아있습니다. 현재 사용되지 않는다면 문제없지만, 실수로 호출될 경우 예상치 못한 동작이 발생할 수 있습니다.구현이 필요 없다면 명시적으로
UnsupportedOperationException을 던지는 것을 고려해보세요:@Override public Optional<UserModel> findById(Long id) { - return Optional.empty(); + throw new UnsupportedOperationException("findById is not implemented yet"); }
30-31: 불필요한 빈 줄을 제거하세요.파일 끝에 불필요한 빈 줄이 있습니다.
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (2)
18-31: 중복된 null 체크와 비효율적인 객체 생성이 있습니다.
- UserService.getUser에서 사용자를 조회한 후 null 체크를 하고, PointService.findPoint 내부에서도 다시 사용자 조회를 합니다 (PointService.java 21-26줄).
- 조회를 위해
new PointModel(user, 0)으로 불필요한 객체를 생성하고 있습니다.PointService의 메서드 시그니처를 개선하면 이 문제들을 해결할 수 있습니다:
public PointInfo getPoint(String userId) { UserModel user = userService.getUser(userId); if (user == null) { throw new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 요청입니다."); } - PointModel pointModel = new PointModel(user, 0); - PointModel point = pointService.findPoint(pointModel); + PointModel point = pointService.findPoint(user); if (point == null) { throw new CoreException(ErrorType.NOT_FOUND, "포인트 정보가 없습니다."); } return PointInfo.from(point); }
33-43: 충전 후 재조회를 제거하세요.
pointService.charge호출 후 업데이트된 포인트를 다시 조회하고 있습니다 (line 41). 이는 불필요한 DB 조회입니다. PointService.charge가 업데이트된 PointModel을 반환하도록 개선하면 재조회를 피할 수 있습니다.PointService.charge가 PointModel을 반환하도록 수정하면:
public PointInfo chargePoint(String userId, int amount) { UserModel user = userService.getUser(userId); if (user == null) { throw new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 요청입니다."); } - PointModel pointModel = new PointModel(user, amount); - pointService.charge(pointModel); - - PointModel charged = pointService.findPoint(new PointModel(user, 0)); + PointModel charged = pointService.charge(user, amount); return PointInfo.from(charged); }apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java (1)
20-20: 헤더 이름을 상수로 추출하는 것을 권장합니다."X-USER-ID" 헤더 이름이 하드코딩되어 있어 유지보수성이 떨어집니다. 두 엔드포인트에서 동일한 헤더를 사용하므로, 상수로 추출하여 일관성을 유지하는 것이 좋습니다.
다음과 같이 상수를 정의할 수 있습니다:
@RequiredArgsConstructor @RestController @RequestMapping("/api/v1/points") public class PointV1Controller implements PointV1ApiSpec { + private static final String USER_ID_HEADER = "X-USER-ID"; private final PointFacade pointFacade; @GetMapping @Override public ApiResponse<PointV1Dto.PointResponse> getPoint( - @RequestHeader(value = "X-USER-ID") String userId + @RequestHeader(value = USER_ID_HEADER) String userId ) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (31)
.docs/design/01-requirements.md(1 hunks).docs/design/02-sequence-diagrams.md(1 hunks).docs/design/03-class-diagram.md(1 hunks).docs/design/04-erd.md(1 hunks)apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java(1 hunks)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/point/PointModel.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.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/point/PointJpaRepository.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.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(2 hunks)apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java(1 hunks)apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Dto.java(1 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/point/PointModelTest.java(1 hunks)apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java(1 hunks)apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java(1 hunks)apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java(1 hunks)apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java(1 hunks)apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-31T14:57:09.157Z
Learnt from: minor7295
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 22
File: apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointsV1Controller.java:24-34
Timestamp: 2025-10-31T14:57:09.157Z
Learning: In the loopers-spring-java-template repository, the X-USER-ID header-based user identification is temporarily used for learning purposes and will be replaced with proper authentication (Spring Security) in a future PR. Do not flag this as a security issue until proper authentication is implemented.
Applied to files:
apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/user/UserService.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
📚 Learning: 2025-10-31T02:20:33.781Z
Learnt from: kilian-develop
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 15
File: core/domain/src/main/java/com/loopers/core/domain/user/vo/UserIdentifier.java:16-27
Timestamp: 2025-10-31T02:20:33.781Z
Learning: In UserIdentifier and similar value objects, when the constructor performs only null-checking while the static create() method performs full validation (regex, length, etc.), this is an intentional pattern for schema evolution. The constructor is used by the persistence layer to reconstruct domain objects from the database (no validation needed for already-validated legacy data), while create() is used by the application layer to create new domain objects (with validation for new data). This allows backward compatibility when validation rules change in production without requiring migration of all existing database records.
Applied to files:
apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
🧬 Code graph analysis (16)
apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (3)
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (1)
RequiredArgsConstructor(12-44)apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java (1)
RequiredArgsConstructor(11-25)apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (1)
RequiredArgsConstructor(10-32)
apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java (2)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java (3)
DisplayName(43-81)DisplayName(83-97)SpringBootTest(21-98)apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java (1)
DisplayName(14-122)
apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java (2)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Dto.java (1)
PointV1Dto(6-20)apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java (1)
SpringBootTest(26-161)
apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java (3)
apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1)
RequiredArgsConstructor(11-31)apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (1)
RequiredArgsConstructor(10-32)apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java (1)
RequiredArgsConstructor(11-38)
apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (4)
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (1)
RequiredArgsConstructor(12-44)apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java (1)
RequiredArgsConstructor(10-28)apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (1)
RequiredArgsConstructor(12-43)apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (1)
RequiredArgsConstructor(10-32)
apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java (1)
Entity(9-59)
apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (2)
apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (1)
RequiredArgsConstructor(12-43)apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1)
RequiredArgsConstructor(11-31)
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (2)
apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (1)
RequiredArgsConstructor(12-43)apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1)
RequiredArgsConstructor(11-31)
apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java (3)
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (1)
RequiredArgsConstructor(12-44)apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java (1)
RequiredArgsConstructor(12-43)apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java (1)
RequiredArgsConstructor(10-32)
apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java (2)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java (1)
DisplayName(16-36)apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java (2)
DisplayName(52-93)DisplayName(95-129)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java (3)
apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java (1)
RequiredArgsConstructor(10-28)apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java (1)
RequiredArgsConstructor(11-31)apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java (1)
UserV1Dto(6-27)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java (2)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java (2)
DisplayName(43-81)DisplayName(83-97)apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java (1)
DisplayName(14-122)
apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java (1)
Entity(12-57)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java (2)
apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java (1)
RequiredArgsConstructor(12-44)apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Dto.java (1)
PointV1Dto(6-20)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java (3)
apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java (1)
DisplayName(16-36)apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java (3)
DisplayName(52-93)DisplayName(95-129)SpringBootTest(25-130)apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java (3)
DisplayName(62-109)DisplayName(111-162)SpringBootTest(23-163)
apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java (1)
UserV1Dto(6-27)
🔇 Additional comments (17)
apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java (2)
15-19: 회원가입 로직은 정상적으로 동작합니다.UserModel 생성 및 저장 로직이 명확합니다. 다만 SignupRequest의 gender 필드가 사용되지 않는 점은 UserV1Dto.java에서 언급했습니다.
21-27: LGTM - 적절한 예외 처리.사용자가 존재하지 않을 때 명확한 에러 메시지와 함께 CoreException을 발생시키는 것이 좋습니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Dto.java (1)
7-14: LGTM - PointResponse 매핑 로직.PointInfo에서 PointResponse로의 변환이 명확하게 구현되어 있습니다.
apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java (1)
86-95: LGTM - 유효하지 않은 사용자 예외 처리 테스트.존재하지 않는 사용자로 포인트 충전 시 CoreException이 발생하는지 확인하는 테스트가 적절합니다.
apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java (1)
11-25: LGTM - 깔끔한 Repository 구현.PointJpaRepository로의 위임이 명확하고 간결합니다. 도메인 레이어와 인프라 레이어 분리가 잘 되어 있습니다.
apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java (5)
23-50: LGTM - 적절한 E2E 테스트 구성.SpringBootTest 설정과 테스트 픽스처 정리가 잘 되어 있습니다. 각 테스트 후 데이터베이스를 정리하는 것이 좋습니다.
67-89: LGTM - 포인트 조회 성공 시나리오 테스트.사용자와 포인트를 생성하고 X-USER-ID 헤더로 조회하는 전체 흐름이 잘 테스트되어 있습니다. assertAll을 사용한 다중 검증도 적절합니다.
93-108: LGTM - 헤더 누락 시나리오 테스트.필수 헤더가 없을 때 400 Bad Request를 반환하는지 확인하는 테스트가 좋습니다.
116-139: LGTM - 포인트 충전 성공 시나리오 테스트.1000 포인트 충전 후 응답 검증이 체계적으로 이루어지고 있습니다.
143-161: LGTM - 존재하지 않는 사용자 예외 테스트.존재하지 않는 사용자로 충전 시도 시 404 Not Found를 반환하는지 확인하는 테스트가 적절합니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java (1)
8-28: LGTM - 명확한 OpenAPI 스펙 정의.User V1 API의 계약이 OpenAPI 어노테이션으로 잘 정의되어 있습니다. 한글로 작성된 설명도 명확합니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java (1)
52-65: 잘 구현되었습니다!새로 추가된 예외 핸들러들이 기존 패턴을 잘 따르고 있으며, 적절한 에러 메시지를 제공합니다. 헤더 누락과 검증 실패에 대한 처리가 명확합니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java (1)
9-31: 잘 작성된 API 명세입니다!Swagger 어노테이션이 적절하게 사용되었고, API 문서가 명확합니다. 인터페이스 기반 설계로 관심사 분리도 잘 되어 있습니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java (1)
29-37: 잘 구현되었습니다!사용자 조회 엔드포인트가 명확하고 간결하게 구현되었습니다.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java (3)
1-8: LGTM!필요한 의존성들이 적절하게 import되어 있습니다.
10-15: LGTM!파사드 패턴을 사용한 깔끔한 컨트롤러 구조입니다. 의존성 주입도 적절합니다.
27-36: 요청 본문 검증은 잘 되어 있습니다.
@Valid어노테이션을 통해ChargeRequest의 검증이 제대로 수행되고 있습니다. 다만, Line 20에서 언급한 X-USER-ID 헤더 검증 이슈가 이 메서드에도 동일하게 적용됩니다.
| ### 2.3. 유스케이스 흐름 | ||
| * **Main Flow (상세 조회):** | ||
| 1. 사용자가 상품 목록에서 특정 상품을 클릭한다. | ||
| 2. 해당 상푸의 상세 정보(이름, 가격, 설명, 총 좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타이포 수정: "상푸" → "상품".
- 2. 해당 상푸의 상세 정보(이름, 가격, 설명, 총 좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다.
+ 2. 해당 상품의 상세 정보(이름, 가격, 설명, 총 좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 2. 해당 상푸의 상세 정보(이름, 가격, 설명, 총 좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다. | |
| 2. 해당 상품의 상세 정보(이름, 가격, 설명, 총 좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다. |
🤖 Prompt for AI Agents
In .docs/design/01-requirements.md around line 46, there is a typo "상푸" that
should be corrected to "상품"; update the text to read "해당 상품의 상세 정보(이름, 가격, 설명, 총
좋아요 수, 나의 좋아요 여부 등)를 조회하여 반환한다." ensuring only the single word is fixed and
punctuation/spacing remains unchanged.
| 1. 사용자가 'A 브랜드'를 조회회한다. | ||
| 2. 해당 브랜드드 상품 목록을 반환한다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타이포 수정: 문법 오류 2건.
- 1. 사용자가 'A 브랜드'를 조회회한다.
- 2. 해당 브랜드드 상품 목록을 반환한다.
+ 1. 사용자가 'A 브랜드'를 조회한다.
+ 2. 해당 브랜드의 상품 목록을 반환한다.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 1. 사용자가 'A 브랜드'를 조회회한다. | |
| 2. 해당 브랜드드 상품 목록을 반환한다. | |
| 1. 사용자가 'A 브랜드'를 조회한다. | |
| 2. 해당 브랜드의 상품 목록을 반환한다. |
🤖 Prompt for AI Agents
In .docs/design/01-requirements.md around lines 70 to 71, fix two typos: change
"조회회한다" to "조회한다" and change "해당 브랜드드 상품 목록을 반환한다." to "해당 브랜드의 상품 목록을 반환한다."
| class Product { | ||
| Long id | ||
| String name | ||
| int price | ||
| int quantity | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
필드명 일관성 검토: ERD와 클래스 다이어그램 동기화 필요.
Product 클래스의 필드명이 ERD와 다릅니다:
- 클래스 다이어그램:
quantity - ERD:
stock_quantity
요구사항 및 시퀀스 다이어그램에서 사용할 정확한 필드명을 결정하고 전체 설계 문서에서 일관되게 사용하세요.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find all design documents in the .docs/design directory
find .docs/design -type f \( -name "*.md" -o -name "*.mmd" -o -name "*.txt" \) | sort
Length of output: 124
🏁 Script executed:
#!/bin/bash
# Search for ERD-related files
fd -e md -e mmd -e txt . .docs/design | grep -i erdLength of output: 22
🏁 Script executed:
#!/bin/bash
# Examine the class diagram file to see Product class definition
cat ".docs/design/03-class-diagram.md"Length of output: 838
🏁 Script executed:
#!/bin/bash
# Search for field name references (quantity, stock_quantity, stock) in all design docs
rg -n "(quantity|stock_quantity|stock)" .docs/design --type mdLength of output: 677
🏁 Script executed:
#!/bin/bash
# List all files in .docs/design to understand document structure
ls -la .docs/design/Length of output: 380
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine the ERD file completely to see Product entity field names
cat ".docs/design/04-erd.md"
Length of output: 918
🏁 Script executed:
#!/bin/bash
# Examine requirements to see specified field names
cat ".docs/design/01-requirements.md"Length of output: 3680
🏁 Script executed:
#!/bin/bash
# Examine sequence diagrams for context
cat ".docs/design/02-sequence-diagrams.md"Length of output: 1977
ERD와 클래스 다이어그램의 필드명 불일치 수정 필요
검증 결과, 설계 문서 간 필드명 불일치가 확인되었습니다:
- ERD (
04-erd.md라인 14):products테이블에stock_quantity정의 - 클래스 다이어그램 (
03-class-diagram.md라인 14):Product클래스에quantity정의
orderitems의 quantity(주문 수량)와 구분하기 위해, Product의 재고 필드명을 명확히 결정하고 ERD, 클래스 다이어그램, 요구사항, 시퀀스 다이어그램 전체에서 일관되게 적용하세요.
🤖 Prompt for AI Agents
In .docs/design/03-class-diagram.md around lines 10 to 15, the Product class
uses the field name "quantity" which conflicts with the ERD's "stock_quantity"
and can be confused with orderitems.quantity; rename Product.quantity to
stock_quantity in this file and update the wording to match the ERD, then review
and make the same rename across related artifacts (04-erd.md, requirements,
sequence diagrams, and any references in docs/tests) so all diagrams and docs
use "stock_quantity" consistently.
| class Order { | ||
| Long id | ||
| User user | ||
| int totalPrice | ||
| Timestamp orderDate | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
필드명 일관성 검토: Order 클래스의 시간 필드.
Order 클래스의 필드명이 ERD와 다릅니다:
- 클래스 다이어그램:
orderDate - ERD:
created_at
데이터베이스 스키마와 도메인 모델의 네이밍을 통일하고, 시퀀스 다이어그램과도 일관성 있게 유지하세요.
🏁 Script executed:
#!/bin/bash
# List all files in .docs/design directory to identify ERD and sequence diagram files
find .docs/design -type f -name "*.md" | sortLength of output: 124
🏁 Script executed:
#!/bin/bash
# Search for ERD-related content showing database field names
fd -e md . .docs/design -x cat {}Length of output: 7413
🏁 Script executed:
#!/bin/bash
# Search for "created_at" or "orderDate" references in design docs
rg -n -i "(created_at|orderDate)" .docs/design/Length of output: 118
🏁 Script executed:
#!/bin/bash
# Look for Order class definitions in the codebase
rg -n -C5 "class Order" --type=java --type=ts --type=js --type=pyLength of output: 0
Order 클래스 시간 필드 네이밍 불일치 확인됨 - 수정 필요
검증 결과 설계 문서에서 다음과 같은 불일치가 확인되었습니다:
- ERD (.docs/design/04-erd.md, 29줄):
created_at - 클래스 다이어그램 (.docs/design/03-class-diagram.md, 28줄):
orderDate
데이터베이스 스키마(snake_case)와 도메인 모델(camelCase) 간 명확한 네이밍 규칙을 수립하거나, 둘 중 하나로 통일하세요. 다른 엔티티(User, Product 등)의 시간 필드도 동일한 원칙으로 일관성 있게 정의되어야 합니다.
🤖 Prompt for AI Agents
In .docs/design/03-class-diagram.md around lines 24–29, the Order class uses
"orderDate" which conflicts with the ERD (.docs/design/04-erd.md line 29) that
defines the timestamp as "created_at"; decide on a consistent convention (either
DB snake_case and domain camelCase mapping rule, or unify both to one style) and
apply it: update the class diagram to use the chosen name (e.g., change
orderDate → createdAt if you keep domain camelCase, or → created_at if you want
exact DB names), then update the ERD or other class diagrams (User, Product,
etc.) accordingly to reflect the same rule and add a brief note in the design
docs describing the naming convention for timestamp fields.
| OrderItem --> Product : (주문 항목은 상품을 가짐) | ||
|
|
||
| Like --> User : (좋아요는 유저를 가짐) | ||
| Like -- > Product : (좋아요는 상품을 가짐) No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mermaid 문법 오류: 관계 정의 수정 필요.
라인 46에서 관계 정의에 오타가 있습니다. 스페이스가 불필요하게 포함되어 있습니다.
- Like -- > Product : (좋아요는 상품을 가짐)
+ Like --> Product : (좋아요는 상품을 가짐)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Like -- > Product : (좋아요는 상품을 가짐) | |
| Like --> Product : (좋아요는 상품을 가짐) |
🤖 Prompt for AI Agents
.docs/design/03-class-diagram.md around line 46: the Mermaid relationship has an
extra space in "Like -- > Product : (좋아요는 상품을 가짐)"; remove the space so the
arrow is "Like --> Product : (좋아요는 상품을 가짐)" to follow correct Mermaid syntax.
| public void use(int amount) { | ||
|
|
||
| if (amount < 0) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "사용 금액은 0보다 커야 합니다."); | ||
| } | ||
| if (point < 0) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "포인트는 0 이상이어야 합니다."); | ||
| } | ||
| this.point -= amount; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용 로직에서 잔액 부족 검증 누락
Line 47~56에서 point가 충분한지 확인하지 않고 차감하고 있어, point=10 상태에서 amount=20을 호출하면 잔액이 -10까지 내려갑니다. 이는 포인트 도메인의 핵심 불변식을 깨뜨리는 치명적 버그입니다. 차감 전에 보유 포인트 이상인지 검증해 주세요.
public void use(int amount) {
if (amount < 0) {
throw new CoreException(ErrorType.BAD_REQUEST, "사용 금액은 0보다 커야 합니다.");
}
- if (point < 0) {
- throw new CoreException(ErrorType.BAD_REQUEST, "포인트는 0 이상이어야 합니다.");
- }
- this.point -= amount;
+ if (amount > this.point) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "사용 금액이 보유 포인트를 초과합니다.");
+ }
+ this.point -= amount;
}🤖 Prompt for AI Agents
In apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
around lines 47-56, the use(int amount) method currently decrements point
without verifying sufficiency; add a validation after the existing non-negative
checks that throws a CoreException(ErrorType.BAD_REQUEST, "보유 포인트가 부족합니다.") (or
similar message) when amount > point, and only subtract amount from point when
the check passes to prevent negative balances.
| public ApiResponse<PointV1Dto.PointResponse> getPoint( | ||
| @RequestHeader(value = "X-USER-ID") String userId | ||
| ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
X-USER-ID 헤더 값 검증 추가가 필요합니다.
현재 @RequestHeader는 헤더의 존재 여부만 확인하고, 빈 문자열이나 공백 문자열에 대한 검증은 수행하지 않습니다. 빈 userId가 파사드로 전달되면 혼란스러운 에러 메시지가 발생할 수 있습니다.
헤더 값 검증을 추가하거나, 파사드 레벨에서 명시적인 검증을 수행하는 것을 권장합니다.
다음과 같이 수정할 수 있습니다:
public ApiResponse<PointV1Dto.PointResponse> getPoint(
- @RequestHeader(value = "X-USER-ID") String userId
+ @RequestHeader(value = "X-USER-ID") @NotBlank(message = "사용자 ID는 필수입니다.") String userId
) {또는 컨트롤러 메서드 내에서 검증:
public ApiResponse<PointV1Dto.PointResponse> getPoint(
@RequestHeader(value = "X-USER-ID") String userId
) {
+ if (userId == null || userId.isBlank()) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "사용자 ID는 필수입니다.");
+ }
PointInfo info = pointFacade.getPoint(userId);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
around lines 19-21, the X-USER-ID header is only checked for presence but not
for empty or blank values; add validation so empty/whitespace-only userId is
rejected. Fix by enforcing non-blank header either via annotation (add
@Validated on the controller and change the parameter to
@RequestHeader("X-USER-ID") @NotBlank String userId) or by explicit check at
method start (if userId == null || userId.trim().isEmpty() throw a 400
BadRequest with a clear message), and ensure the controller returns a proper 400
response rather than forwarding an empty userId to the facade.
| @PostMapping("/signup") | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @Override | ||
| public ApiResponse<UserV1Dto.UserResponse> signup( | ||
| @Valid @RequestBody UserV1Dto.SignupRequest request | ||
| ) { | ||
| UserInfo info = userFacade.signup(request.userId(), request.email(), request.birthDate()); | ||
| UserV1Dto.UserResponse response = UserV1Dto.UserResponse.from(info); | ||
| return ApiResponse.success(response); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gender 필드가 누락되어 데이터가 손실됩니다.
SignupRequest에서 gender를 필수 필드로 검증하지만 (UserV1Dto.java 17줄), userFacade.signup에 전달하지 않아 데이터가 저장되지 않습니다. 사용자가 입력한 gender 정보가 유실됩니다.
다음과 같이 수정하세요:
- UserInfo info = userFacade.signup(request.userId(), request.email(), request.birthDate());
+ UserInfo info = userFacade.signup(request.userId(), request.email(), request.gender(), request.birthDate());UserFacade와 UserModel, UserService도 함께 수정이 필요합니다.
Committable suggestion skipped: line range outside the PR's diff.
| @NotBlank(message = "gender는 필수입니다.") | ||
| String gender, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
gender 필드가 사용되지 않는지 확인하세요.
SignupRequest에 gender 필드가 있지만, UserFacade.signup 메서드(Line 16)에서 UserModel 생성 시 이 필드를 사용하지 않습니다. 필요 없는 필드라면 제거하거나, 사용해야 한다면 UserModel에 반영해야 합니다.
다음 스크립트로 gender 필드의 사용처를 확인하세요:
🏁 Script executed:
#!/bin/bash
# gender 필드가 실제로 사용되는지 확인
rg -n "\.gender\(\)" --type=java -C3Length of output: 0
더 정확한 검증을 위해 추가 조사가 필요합니다. gender 필드의 사용처를 더 광범위하게 확인하겠습니다.
---
🏁 Script executed:
```shell
#!/bin/bash
# gender 필드 검색 - 더 광범위한 패턴으로 확인
echo "=== gender 필드 선언 및 사용 검색 ==="
rg -n "gender" --type=java -C2
echo ""
echo "=== UserFacade.signup 메서드 확인 ==="
rg -n "UserFacade|signup" --type=java -A10 -B2 | head -100
echo ""
echo "=== UserModel 클래스 확인 ==="
rg -n "class UserModel" --type=java -A20
Length of output: 16768
SignupRequest 클래스를 더 자세히 확인하여 최종 분석을 완료하겠습니다.
---
🏁 Script executed:
```shell
#!/bin/bash
# SignupRequest 전체 구조 확인
rg -n "record SignupRequest\|class SignupRequest" --type=java -A15
Length of output: 0
---
🏁 Script executed:
```shell
#!/bin/bash
# UserV1Dto.java 전체 파일 내용 확인
cat apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
Length of output: 784
gender 필드를 SignupRequest에서 제거하거나 전체 처리 흐름에 반영하세요.
gender는 SignupRequest에서 필수 필드(@notblank)로 선언되어 있지만, UserV1Controller에서 UserFacade.signup() 호출 시 전달되지 않습니다. UserFacade와 UserModel에도 gender 매개변수와 필드가 없어 데이터가 버려집니다.
- SignupRequest가 필요 없는 필드라면 gender 제거
- 필요하다면 UserFacade.signup()과 UserModel에 gender 추가
🤖 Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
around lines 22-23 the gender field is annotated @NotBlank but never propagated
to UserFacade/UserModel; either remove the unused field from the DTO or wire it
through the signup flow. If removing: delete the gender field and its
validation, update any clients/tests and mapping code that reference it. If
keeping: add a gender parameter to UserFacade.signup(...) signature and
corresponding DTO-to-model mapping, add a gender field to UserModel and any
persistence/repository objects and update controller calls to pass gender, and
ensure validation annotations remain consistent.
| UserV1Dto.SignupRequest request = new UserV1Dto.SignupRequest( | ||
| "user123", | ||
| "user123@example.com", | ||
| "male", | ||
| "1999-01-01" | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
gender 필드가 사용되지 않는 문제를 확인하세요.
테스트에서 gender 필드를 제공하고 있지만 (line 72), UserFacade.signup 메서드는 userId, email, birthDate만 전달하고 gender는 사용하지 않습니다. SignupRequest에서 gender를 필수(@notblank)로 검증하지만 실제로 저장되지 않아 데이터 손실이 발생합니다.
다음 스크립트로 gender 필드의 사용 여부를 확인해보세요:
🏁 Script executed:
#!/bin/bash
# UserModel에 gender 관련 필드나 메서드가 있는지 확인
ast-grep --pattern 'class UserModel {
$$$
}'
# UserFacade.signup에서 gender 파라미터 사용 여부 확인
ast-grep --pattern 'public UserInfo signup($$$) {
$$$
}'Length of output: 654
추가 검증이 필요합니다. 다음을 확인하겠습니다:
- UserModel 클래스 구조와 gender 필드
- SignupRequest DTO 정의
- gender 필드의 사용 흐름
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# UserModel 클래스 정의 및 필드 확인
ast-grep --pattern 'class UserModel {
$$$
}' -l
# 파일 찾기 및 내용 확인
fd UserModel.java --type f -x cat {}
# SignupRequest DTO 구조 확인
fd SignupRequest -l
# gender 필드가 사용되는 모든 위치 확인
rg -n 'gender' --type java apps/commerce-api/src/main/javaLength of output: 2086
추가 검증이 필요합니다. 요청 처리 흐름을 확인하겠습니다:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# UserV1 API 컨트롤러에서 gender 처리 확인
fd UserV1Controller -l -x cat {}
# 테스트에서 응답 검증 내용 확인
rg -n -A 10 'SignupRequest' apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.javaLength of output: 1332
gender 필드 데이터 손실 문제 확인됨 - 수정 필요
검증 결과:
- SignupRequest DTO: gender 필드를
@NotBlank(필수)로 검증 (UserV1Dto.java) - UserModel: gender 필드 존재하지 않음 - userId, email, birthDate만 보유
- UserFacade.signup(): 3개 파라미터(userId, email, birthDate)만 수용
- 데이터 흐름: 테스트에서 gender를 API 요청으로 전송하지만 UserModel 저장 시 완전히 버려짐
해결 방안 (다음 중 선택):
- 옵션 A: UserModel에 gender 필드 추가 + UserFacade.signup()에 gender 파라미터 추가 + 데이터 흐름 연결
- 옵션 B: SignupRequest에서 gender 필드 제거 및 @notblank 검증 제거
🤖 Prompt for AI Agents
In
apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
around lines 69 to 74, the test sends a gender value but the application
discards it because UserModel lacks a gender field and UserFacade.signup only
accepts userId, email, birthDate; fix by either (A) adding gender to the domain
and propagation path: add a gender field to UserModel, update
UserV1Dto.SignupRequest -> UserFacade.signup signature to include gender, update
service/repository mapping and persistence to store gender, and adjust tests
accordingly; or (B) remove gender from SignupRequest and related @NotBlank
validation, update API tests to stop sending gender, and ensure DTOs,
controllers, and validation reflect removal.
📌 Summary
2주차 과제인 이커머스 도메인(상품, 브랜드, 좋아요, 주문 등)에 대한 설계를 진행했습니다.
각 도메인 별 시나리오 정의서 , 요구사항명세서, 시퀀스 다이어그램, 클래스 다이어그램, ERD 작성하였습니다.
💬 Review Points
✅ Checklist
📎 References
Summary by CodeRabbit
릴리스 노트
새로운 기능
문서