Skip to content

Conversation

@dame2
Copy link

@dame2 dame2 commented Feb 5, 2026

📌 Summary

  • 배경: 커머스 API에 사용자 관리 기능이 없어 회원가입, 인증, 비밀번호 관리가 불가능한 상태
  • 목표: User 도메인을 TDD 방식으로 구현하여 회원가입, 내 정보 조회, 비밀번호 변경 API 제공
  • 결과: 3개 REST API 엔드포인트 제공, BCrypt 비밀번호 암호화, 헤더 기반 인증, 단위/통합/E2E 테스트 68건 통과

🧭 Context & Decision

문제 정의

  • 현재 동작/제약: 사용자 관련 도메인이 존재하지 않아 인증/인가 기능 없음
  • 문제(또는 리스크): 사용자 식별 없이는 커머스 기능(주문, 장바구니 등) 구현 불가
  • 성공 기준(완료 정의): 회원가입/내 정보 조회/비밀번호 변경 API가 정상 동작하며, 단위(47건)/통합(9건)/E2E(12건) 테스트 전수 통과

선택지와 결정

  • 고려한 대안:
    • A: Spring Security 필터 체인 기반 인증
    • B: 커스텀 ArgumentResolver를 활용한 헤더 기반 인증
  • 최종 결정: B (커스텀 ArgumentResolver)
  • 트레이드오프: Spring Security의 풍부한 기능을 포기하는 대신 현 요구사항에 맞는 단순한 구조 유지. 비밀번호 암호화만 spring-security-crypto의 BCryptPasswordEncoder 사용
  • 추후 개선 여지: JWT 토큰 기반 인증 전환 시 Spring Security 도입 검토

🏗️ Design Overview

변경 범위

  • 영향 받는 모듈/도메인: commerce-api, modules/jpa (testFixtures)
  • 신규 추가:
    • Domain: User, UserRepository, UserService
    • Infrastructure: UserJpaRepository, UserRepositoryImpl
    • Application: UserFacade, UserInfo
    • Interface: UserV1Controller, UserV1Dto, UserV1ApiSpec
    • Auth: AuthenticatedUser, AuthenticatedUserArgumentResolver, WebMvcConfig
    • Test: UserTest(47건), UserServiceIntegrationTest(9건), UserV1ApiE2ETest(12건)
  • 제거/대체: 없음

주요 컴포넌트 책임

  • User: 사용자 엔티티 — 필드 검증, BCrypt 암호화, 비밀번호 매칭, 이름 마스킹
  • UserService: 회원가입(중복 체크), 조회, 인증, 비밀번호 변경 도메인 로직
  • UserFacade: Application 계층에서 UserService 조합 → UserInfo 반환
  • AuthenticatedUserArgumentResolver: X-Loopers-LoginId/LoginPw 헤더 → AuthenticatedUser 변환
  • UserV1Controller: REST API 엔드포인트 (POST /users, GET /users/me, PATCH /users/me/password)

🔁 Flow Diagram

회원가입 (POST /api/v1/users)

sequenceDiagram
  autonumber
  participant Client
  participant UserV1Controller
  participant UserFacade
  participant UserService
  participant UserRepository
  participant User
  participant DB

  Client->>UserV1Controller: POST /api/v1/users {loginId, password, name, birthDate, email}
  UserV1Controller->>UserFacade: register(...)
  UserFacade->>UserService: register(...)
  UserService->>UserRepository: existsByLoginId(loginId)
  UserRepository->>DB: SELECT EXISTS
  DB-->>UserRepository: result
  alt loginId 중복
    UserService-->>Client: 409 CONFLICT
  else loginId 사용 가능
    UserService->>User: new User(...) — 필드 검증 + BCrypt 암호화
    UserService->>UserRepository: save(user)
    UserRepository->>DB: INSERT
    DB-->>UserRepository: User (with ID)
    UserRepository-->>UserService: User
    UserService-->>UserFacade: User
    UserFacade-->>UserV1Controller: UserInfo
    UserV1Controller-->>Client: 201 Created {userId}
  end
Loading

내 정보 조회 (GET /api/v1/users/me)

sequenceDiagram
  autonumber
  participant Client
  participant ArgumentResolver
  participant UserV1Controller
  participant UserFacade
  participant UserService
  participant UserRepository
  participant DB

  Client->>ArgumentResolver: GET /api/v1/users/me (X-Loopers-LoginId, X-Loopers-LoginPw)
  alt 헤더 누락
    ArgumentResolver-->>Client: 400 Bad Request
  else 헤더 존재
    ArgumentResolver->>UserV1Controller: AuthenticatedUser(loginId, password)
    UserV1Controller->>UserFacade: getMyInfo(loginId, password)
    UserFacade->>UserService: authenticate(loginId, password)
    UserService->>UserRepository: findByLoginId(loginId)
    UserRepository->>DB: SELECT
    alt 사용자 없음
      UserService-->>Client: 401 USER_NOT_FOUND
    else 비밀번호 불일치
      UserService-->>Client: 401 PASSWORD_MISMATCH
    else 인증 성공
      UserService-->>UserFacade: User
      UserFacade-->>UserV1Controller: UserInfo (maskedName)
      UserV1Controller-->>Client: 200 OK {loginId, name(마스킹), birthDate, email}
    end
  end
Loading

비밀번호 변경 (PATCH /api/v1/users/me/password)

sequenceDiagram
  autonumber
  participant Client
  participant ArgumentResolver
  participant UserV1Controller
  participant UserFacade
  participant UserService
  participant User
  participant DB

  Client->>ArgumentResolver: PATCH /api/v1/users/me/password (Headers + Body)
  alt 헤더 누락
    ArgumentResolver-->>Client: 400 Bad Request
  else 헤더 존재
    ArgumentResolver->>UserV1Controller: AuthenticatedUser + ChangePasswordRequest
    UserV1Controller->>UserFacade: changePassword(loginId, currentPw, newPw)
    UserFacade->>UserService: changePassword(loginId, currentPw, newPw)
    UserService->>UserService: authenticate(loginId, currentPw)
    alt 인증 실패
      UserService-->>Client: 401 Unauthorized
    else 인증 성공
      UserService->>User: changePassword(newPw)
      alt 현재 비밀번호와 동일 or 규칙 위반
        User-->>Client: 400 Bad Request
      else 변경 성공
        Note over User: BCrypt 재암호화
        UserService->>DB: UPDATE (dirty checking)
        UserV1Controller-->>Client: 200 OK {message}
      end
    end
  end
Loading

hanyoung-kurly and others added 7 commits February 2, 2026 01:26
- CLAUDE.md 추가 (프로젝트 컨텍스트 및 개발 규칙)
- spring-security-crypto 의존성 추가
- ErrorType에 UNAUTHORIZED, USER_NOT_FOUND, PASSWORD_MISMATCH 추가
- MySqlTestContainersConfig에 MYSQL_ROOT_PASSWORD 환경변수 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- User 엔티티 (필드 검증, BCrypt 암호화, 이름 마스킹)
- UserRepository 인터페이스
- UserService (회원가입, 조회, 인증, 비밀번호 변경)
- UserTest 단위 테스트 47건

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- UserJpaRepository (Spring Data JPA)
- UserRepositoryImpl (Repository 구현체)
- UserServiceIntegrationTest 통합 테스트 9건

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- UserFacade, UserInfo (Application 계층)
- AuthenticatedUser, AuthenticatedUserArgumentResolver (헤더 인증)
- WebMvcConfig (ArgumentResolver 등록)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- UserV1Controller (POST /users, GET /users/me, PATCH /users/me/password)
- UserV1Dto (요청/응답 DTO)
- UserV1ApiSpec (OpenAPI 스펙)
- UserV1ApiE2ETest E2E 테스트 12건
- user-v1.http (IntelliJ HTTP Client)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- .claude/commands/create-pr.md (PR 템플릿 기반 자동 생성 스킬)

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

coderabbitai bot commented Feb 5, 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.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

2 participants