[Volume 1] 회원가입, 내 정보 조회, 비밀번호 변경 기능 구현#36
Open
Praesentia-YKM wants to merge 9 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Open
[Volume 1] 회원가입, 내 정보 조회, 비밀번호 변경 기능 구현#36Praesentia-YKM wants to merge 9 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Praesentia-YKM wants to merge 9 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Conversation
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>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
✨ 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
📌 Summary
배경:
목표:
결과:
💬 리뷰 희망 부분
1. Value Object 설계 — String vs VO, 어디까지 감쌀 것인가
배경 및 문제 상황
loginId에 특수문자가 들어오거나,email에 잘못된 형식이 들어와도 엔티티 생성 시점에는 알 수 없음해결 방안
@EmbeddableVO로 캡슐화하여 생성자에서 검증구현 세부사항
CoreException을 던짐@Embeddable+@AttributeOverride로 DB 컬럼 매핑고민한 점
2. Password 처리 전략 — 검증용 VO와 저장용 String의 분리
배경 및 문제 상황
@Embeddable로 만들면 자연스러울 것 같지만, DB에는 BCrypt 인코딩된 값이 저장됨해결 방안
String으로 저장Password.of(rawPw, birthDate)new Password(newRawPw)→validateAgainst(birthDate)관련 코드
고민한 점
3. 도메인 서비스 분리 — 단일 서비스 vs 기능별 분리
배경 및 문제 상황
MemberService에 모든 로직을 넣으면 클래스가 비대해지고, 의존성이 혼재됨PasswordEncoder만 필요, 인증은PasswordEncoder+ 조회, 비밀번호 변경은 전부 필요해결 방안
MemberFacade가 오케스트레이션MemberSignupServiceMemberAuthServiceMemberPasswordService관련 코드
고민한 점
MemberAuthService만 수정하면 됨4. 비밀번호 변경 검증 순서
배경 및 문제 상황
해결 방안
new Password(newRawPw)) → 형식이 틀리면 이후 검증 불필요validateAgainst) → 최종 정책 검증관련 코드
고민한 점
Password.of()를 사용하지 않은 이유는,of()는 형식+생년월일을 한 번에 검증하지만 여기서는 그 사이에 동일 PW 체크가 끼어야 해서 분리PASSWORD_MISMATCH/INVALID_PASSWORD/PASSWORD_SAME_AS_OLD/PASSWORD_CONTAINS_BIRTH_DATE)하여 클라이언트가 어떤 검증에서 실패했는지 정확히 알 수 있도록 했는데, 반대로INVALID_PASSWORD하나로 통합하면 비밀번호 정책 내부 구현을 숨길 수 있는 장점도 있음. 이 수준의 에러코드 세분화가 적절한지 의견 부탁드립니다🏗️ 변경점
신규 추가:
MemberModelLoginId,Email,MemberNamePasswordMemberSignupServiceMemberAuthServiceMemberPasswordServiceMemberRepositoryMemberFacadeMemberInfoMemberV1ControllerMemberV1DtoMemberV1ApiSpecMemberRepositoryImplMemberJpaRepositoryPasswordEncoderConfig제거/대체:
🔁 Flow Diagram
1. POST /api/v1/members (회원가입)
sequenceDiagram participant Client participant Controller as MemberV1Controller participant Facade as MemberFacade participant SignupSvc as MemberSignupService participant Repository as MemberRepository Client->>Controller: POST /api/v1/members<br/>Body: {loginId, password, name, birthDate, email} Controller->>Facade: signup(loginId, password, name, birthDate, email) Facade->>SignupSvc: signup(loginId, rawPassword, name, birthDate, email) Note over SignupSvc: Value Object 생성 (자동 검증)<br/>LoginId(loginId) - 영문+숫자 검증<br/>MemberName(name) - 빈값 검증<br/>Email(email) - 형식 검증<br/>Password.of(rawPw, birthDate) - 길이/문자/생년월일 검증 alt VO 검증 실패 SignupSvc-->>Client: CoreException (INVALID_LOGIN_ID / INVALID_PASSWORD / ...) else 검증 통과 SignupSvc->>Repository: findByLoginId(loginId) alt loginId 중복 SignupSvc-->>Client: CoreException (DUPLICATE_LOGIN_ID) else 중복 아님 SignupSvc->>SignupSvc: passwordEncoder.encode(rawPassword) SignupSvc->>SignupSvc: new MemberModel(loginIdVo, encodedPw, nameVo, birthDate, emailVo) SignupSvc->>Repository: save(memberModel) Repository-->>SignupSvc: MemberModel SignupSvc-->>Facade: MemberModel Facade->>Facade: MemberInfo.from(member) Facade-->>Controller: MemberInfo Controller-->>Client: ApiResponse {loginId, name, birthDate, email} end end2. GET /api/v1/members/me (내 정보 조회)
sequenceDiagram participant Client participant Controller as MemberV1Controller participant Facade as MemberFacade participant AuthSvc as MemberAuthService participant Repository as MemberRepository Client->>Controller: GET /api/v1/members/me<br/>Headers: X-Loopers-LoginId, X-Loopers-LoginPw Controller->>Facade: getMyInfo(loginId, password) Facade->>AuthSvc: authenticate(loginId, password) AuthSvc->>Repository: findByLoginId(loginId) alt 회원 없음 AuthSvc-->>Client: CoreException (MEMBER_NOT_FOUND) else 회원 존재 AuthSvc->>AuthSvc: member.matchesPassword(password, encoder) alt 비밀번호 불일치 AuthSvc-->>Client: CoreException (AUTHENTICATION_FAILED) else 인증 성공 AuthSvc-->>Facade: MemberModel Facade->>Facade: MemberInfo.fromWithMaskedName(member) Note over Facade: 이름 마스킹 처리 (홍길동 → 홍길*) Facade-->>Controller: MemberInfo Controller-->>Client: ApiResponse {loginId, maskedName, birthDate, email} end end3. PATCH /api/v1/members/me/password (비밀번호 변경)
sequenceDiagram participant Client participant Controller as MemberV1Controller participant Facade as MemberFacade participant AuthSvc as MemberAuthService participant PwSvc as MemberPasswordService participant Repository as MemberRepository Client->>Controller: PATCH /api/v1/members/me/password<br/>Headers: X-Loopers-LoginId, X-Loopers-LoginPw<br/>Body: {currentPassword, newPassword} Controller->>Facade: changePassword(loginId, password, currentPw, newPw) Facade->>AuthSvc: authenticate(loginId, password) Note over AuthSvc: 헤더 기반 인증 (loginId 조회 + PW 매칭) AuthSvc-->>Facade: MemberModel (인증된 회원) Facade->>PwSvc: changePassword(member, currentPw, newRawPw) PwSvc->>PwSvc: member.matchesPassword(currentPw, encoder) alt 현재 비밀번호 불일치 PwSvc-->>Client: CoreException (PASSWORD_MISMATCH) else 일치 PwSvc->>PwSvc: new Password(newRawPw) - 형식 검증 PwSvc->>PwSvc: member.matchesPassword(newRawPw, encoder) alt 새 비밀번호 == 기존 비밀번호 PwSvc-->>Client: CoreException (PASSWORD_SAME_AS_OLD) else 다름 PwSvc->>PwSvc: password.validateAgainst(member.birthDate()) - 생년월일 검증 alt 생년월일 포함 PwSvc-->>Client: CoreException (PASSWORD_CONTAINS_BIRTH_DATE) else 검증 통과 PwSvc->>PwSvc: member.changePassword(encoder.encode(newRawPw)) PwSvc->>Repository: save(member) PwSvc-->>Facade: void Facade-->>Controller: void Controller-->>Client: ApiResponse SUCCESS end end endAPI 요약
/api/v1/members/api/v1/members/me/api/v1/members/me/password🧪 테스트 전략
3계층 테스트 구조
주요 검증 시나리오
✅ Checklist
📎 기타 참고 사항