Skip to content

Conversation

@SeongHo5356
Copy link
Contributor

@SeongHo5356 SeongHo5356 commented Dec 31, 2025

🚀 Why - 해결하려는 문제가 무엇인가요?

  • Auth/Member 주변의 클래스/메서드/DTO 네이밍이 일관되지 않고, 토큰 처리 로직도 Provider 안에 섞여 있어 가독성과 유지보수성이 떨어졌습니다.
  • 로그아웃(블랙리스트) 토큰 체크, 보안 핸들러 응답 포맷, Swagger(ApiDoc) 에러 응답 등이 흩어져 있어 동작/문서/테스트가 서로 어긋날 여지가 있었습니다.
  • Order 결제/취소 흐름에서 웹 요청 DTO에 대한 의존이 남아 있어 계층 간 결합도를 낮추고 싶었습니다.
  • 전반적으로 “패키지 구조/네이밍 규칙”을 정리해 코드 컨벤션을 강제하고, 이후 확장/리팩터링 비용을 줄이려는 목적이었습니다.

✅ What - 무엇이 변경됐나요?

  • Auth를 Member 하위 도메인 관점으로 정리하고 패키지 구조를 컨벤션에 맞게 리팩터링
  • TokenProvider / TokenResolver 분리
    • 요청에서 토큰 추출은 AuthTokenResolver
    • 토큰 생성/검증은 TokenProvider(JwtTokenProvider)로 역할 분리
  • 토큰 검증 강화
    • JWT 유효성 + (Redis 기반) 로그아웃/블랙리스트 토큰 체크 추가
  • 함수/유스케이스/DTO 네이밍 규칙 정리
    • 유스케이스 메서드는 “행위(동사) 중심” 네이밍으로 통일
    • auth 입력 DTO는 *Input 규칙으로 정리
    • 응답 DTO 일부 분리/정리(예: expertReport 응답 DTO 분리)
  • 보안 핸들러 응답 통일
    • AccessDenied / AuthenticationEntryPoint 응답을 공통 ApiResponse 형태로 정리
  • Swagger(ApiDoc) 에러 응답 예시 정리
    • 로그인/로그아웃/재발급 등 주요 케이스의 에러 응답 예시를 문서에 추가
  • Order 결제 포트 분리 & 취소 요청 의존 제거
    • 결제 게이트웨이 포트를 명확히 하고, cancel 시 웹 요청 DTO 의존 대신 파라미터 기반으로 정리
  • principal 공용화
    • 컨트롤러에서 @AuthenticationPrincipal로 받는 타입을 공용 principal(AuthenticatedMember)로 통일하는 방향으로 정리

🛠️ How - 어떻게 해결했나요?

  • JwtTokenProvider에서 요청 파싱 책임을 제거하고, AuthTokenResolver로 “헤더/쿠키 등에서 토큰 추출”을 위임했습니다.
  • JwtFilter에서
    • tokenResolver.resolveAccessToken(request)로 토큰을 가져온 뒤,
    • tokenProvider.validateToken(token) + redisClient.getValue(token) == null(로그아웃 토큰 여부)로 2중 검증하도록 개선했습니다.
  • 보안 핸들러(JwtAccessDeniedHandler, JwtAuthenticationHandler)가 sendError로 흩뿌리던 응답을 ApiResponse JSON 응답으로 통일했습니다.
  • 함수/DTO 네이밍은 개발가이드에 규칙을 명시하고, 실제 코드에 적용해 컨벤션이 코드로 고정되게 했습니다.
  • Order 취소는 서비스 시그니처를 cancel(orderCode, reason) 형태로 단순화하여 웹 계층 DTO 결합을 제거했습니다.
  • 변경 범위가 넓은 만큼 auth 슬라이스 테스트/유닛 테스트를 보강해 회귀를 방지했습니다.

🖼️ Attachment

💬 기타 코멘트

  • 변경 범위가 넓어 리네이밍/패키지 이동이 많습니다. (리뷰 시 커밋 단위로 보는 것을 추천)
  • 테스트:
    • ./gradlew test
    • (선택) auth 관련 테스트: AuthControllerSliceTest, JwtTokenProviderTest
  • 주의:
    • 내부 DTO/메서드명 변경이 포함되어 있어, 다른 모듈에서 참조 중인 타입/메서드가 있다면 컴파일 에러로 확인 후 함께 정리 필요

Summary by CodeRabbit

  • Refactor

    • 인증·회원 모듈 구조 및 토큰 흐름을 통합해 인증 처리와 오류 응답이 일관화되었습니다.
    • 결제 연동을 포트 기반으로 변경해 결제 흐름이 정리되었습니다.
  • New Features

    • 회원가입/로그인/로그아웃/토큰재발급용 새로운 인증 API와 토큰 해석 컴포넌트가 추가되었습니다.
    • 인증 실패(401) 및 권한 없음(403) 응답이 JSON 형식으로 표준화되어 반환됩니다.
  • Style

    • DTO·메서드 명명 규칙과 조회 메서드 명칭이 일관되게 정리되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

- AuthenticatedMember 도입으로 어댑터 간 결합 완화
- Auth reissue 인자 memberId로 단순화
- MemberQueryUseCase/Service 네이밍 및 테스트 리팩토링
- Member 컨트롤러 조회 로직 정리
@SeongHo5356 SeongHo5356 self-assigned this Dec 31, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

인증·회원 기능을 member.auth 도메인으로 재배치하고 포트/DTO 네이밍을 통일했습니다. 토큰 발급·재발급·로그아웃 흐름과 토큰 추출기(AuthTokenResolver), JWT 필터/핸들러가 리팩터링되었고 다수의 메서드명(getOrThrow→findByIdOrThrow, Dto→Result)·컨트롤러·테스트가 이에 맞게 변경되었습니다. (요약 48단어)

Changes

Cohort / File(s) 설명
인증 도메인 이동·신규 타입
src/main/java/starlight/shared/auth/AuthenticatedMember.java, src/main/java/starlight/application/member/auth/..., src/main/java/starlight/adapter/member/auth/...
인증 관련 패키지 이동(starlight.adapter.auth.* → starlight.adapter.member.auth.*), AuthenticatedMember 추가, AuthUseCase/AuthServiceImpl/AuthTokenResult/SignInInput/SignUpInput 등 신규·재정의
컨트롤러·Swagger 시그니처 변경
src/main/java/starlight/adapter/*/webapi/*Controller.java, src/main/java/starlight/adapter/*/webapi/swagger/*ApiDoc.java
@AuthenticationPrincipal 파라미터 타입 변경(AuthDetails → AuthenticatedMember 또는 이동된 AuthDetails), AuthController 삭제/신규 추가, Swagger 인터페이스 갱신
토큰 추출기·토큰 프로바이더 리팩터링
src/main/java/starlight/adapter/member/auth/webapi/AuthTokenResolver.java, src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java, src/main/java/starlight/application/member/auth/required/TokenProvider.java
헤더 토큰 추출 책임을 분리(AuthTokenResolver), TokenProvider 메서드명(issue/reissue/logout 등)·반환 DTO(AuthTokenResult) 변경, prefix 처리 및 deprecated 주석 추가
JWT 핸들러·필터 재작성
src/main/java/starlight/adapter/member/auth/security/handler/*, src/main/java/starlight/adapter/member/auth/security/filter/*
AccessDenied/AuthenticationEntryPoint 재작성(응답 JSON 직렬화), JwtFilter에 Redis KeyValueMap·AuthTokenResolver 도입 및 검증 로직 보강
포트·퍼시스턴스 명명 통일
src/main/java/starlight/application/*/required/*, src/main/java/starlight/adapter/*/persistence/*
getOrThrow → findByIdOrThrow 등 명명 통일, Repository → Query/Command Port 분리(MemberQueryPort/MemberCommandPort), MemberJpa 등 포트 구현 변경
DTO·레코드 네이밍/형식 변경
src/main/java/starlight/application/*/provided/dto/*, src/main/java/starlight/adapter/*/webapi/dto/*
*Dto → *Result, FeedbackRequestDto → FeedbackRequestInput, 웹/앱 레이어의 Input/Result 규칙 적용 및 변환 메서드 업데이트
결제 클라이언트 포트화
src/main/java/starlight/adapter/order/toss/TossClient.java, src/main/java/starlight/application/order/required/PaymentGatewayPort.java, src/main/java/starlight/application/order/provided/dto/TossClientResult.java
TossClient가 PaymentGatewayPort 구현으로 변경, TossClientResponse → TossClientResult로 DTO명 변경, confirm/cancel 시그니처와 검증/예외 처리 조정
expert-report 관련 리네이밍 및 매핑 변경
src/main/java/starlight/adapter/expertReport/persistence/*, .../webapi/*, src/main/java/starlight/application/expertReport/provided/dto/ExpertReportWithExpertResult.java
Repository/JPA/서비스/컨트롤러에서 메서드명(예: findByTokenWithCommentsOrThrow), DTO(ExpertReportWithExpertDto → ExpertReportWithExpertResult) 및 매핑(applicationCount 포함) 반영
이벤트·이메일 DTO 연계 변경
src/main/java/starlight/application/expertApplication/event/*, src/main/java/starlight/adapter/expertApplication/email/SMTPEmailSender.java
FeedbackRequestDto → FeedbackRequestInput 이름 변경 및 관련 시그니처·이벤트/리스너/이메일 전송자 반영
테스트·문서·가이드 업데이트
src/test/..., 개발가이드.md
패키지·인터페이스·메서드명·DTO 변경을 테스트 전반에 반영, 개발가이드에 포트·네이밍 규칙 문서화

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant AuthUseCase
  participant TokenProvider
  participant Redis

  rect rgba(0,128,128,0.06)
    Note over Client,AuthController: Sign-in 흐름 (리팩터링)
    Client->>AuthController: POST /v1/auth/sign-in (SignInInput)
    AuthController->>AuthUseCase: signIn(SignInInput)
    AuthUseCase->>TokenProvider: issueTokens(member)
    TokenProvider-->>AuthUseCase: AuthTokenResult(access, refresh)
    AuthUseCase->>Redis: setValue(refreshToken, memberId, expiry)
    Redis-->>AuthUseCase: OK
    AuthUseCase-->>AuthController: AuthTokenResult
    AuthController-->>Client: 200 ApiResponse(TokenResponse.from(...))
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🧵 REFACTOR, :memo: DOCS

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 변경 내용의 핵심을 명확하게 요약합니다. 'Member, Auth 도메인 패키지 구조를 정리한다'는 광범위한 리팩토링의 주요 목표를 간결하게 표현하며, 패키지 구조 정리라는 핵심 의도를 효과적으로 전달합니다.
✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/starlight/adapter/member/auth/security/oauth2/OAuth2Attributes.java (2)

31-38: 프로바이더 ID 검증 로직에 잠재적 이슈가 있습니다.

Line 31의 String.valueOf(response.getOrDefault("id", ""))id 값이 명시적으로 null인 경우 "null" 문자열을 반환합니다. 이는 isBlank() 검증을 우회하게 됩니다.

더 안전한 접근법은 다음과 같습니다:

🔎 권장 수정안
-String id = String.valueOf(response.getOrDefault("id", ""));
+Object idObj = response.get("id");
+String id = (idObj != null) ? idObj.toString() : "";

또는 null 체크를 명시적으로 추가:

 String id = String.valueOf(response.getOrDefault("id", ""));
 
-if (id.isBlank()) {
+if (id == null || id.isBlank() || "null".equals(id)) {
     throw new OAuth2AuthenticationException(new OAuth2Error("invalid_provider_id"), "Missing provider id");
 }

45-52: Kakao 프로바이더에도 동일한 검증 이슈가 있습니다.

Line 45에서 네이버와 동일한 String.valueOf() 패턴을 사용하므로, 명시적 null 값에 대한 처리가 필요합니다.

🔎 권장 수정안
-String id = String.valueOf(attributes.getOrDefault("id", ""));
+Object idObj = attributes.get("id");
+String id = (idObj != null) ? idObj.toString() : "";
🧹 Nitpick comments (17)
src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java (1)

14-14: 인증 추상화 인터페이스로의 전환이 잘 적용되었습니다.

API 문서가 구체 타입(AuthDetails)에서 인터페이스(AuthenticatedMember)로 변경되어 더 나은 추상화를 제공합니다. 이는 의존성 역전 원칙(DIP)을 잘 따르는 설계입니다.

🔎 변수명 일관성 개선 제안

Line 49의 파라미터 변수명이 authDetails로 되어 있는데, 타입이 AuthenticatedMember로 변경되었으므로 변수명도 타입과 일관되게 변경하는 것을 고려해보세요:

 ApiResponse<PreSignedUrlResponse> getPresignedUrl(
-        @AuthenticationPrincipal AuthenticatedMember authDetails,
+        @AuthenticationPrincipal AuthenticatedMember authenticatedMember,
         @io.swagger.v3.oas.annotations.Parameter(description = "파일명", required = true) @RequestParam String fileName
 );

Also applies to: 49-49

src/main/java/starlight/application/member/auth/provided/dto/AuthMemberResult.java (1)

1-20: 인증 결과 DTO 설계가 적절합니다.

AuthMemberResult 레코드가 도메인 엔티티(Member)와 애플리케이션 계층을 명확히 분리하고 있습니다. application.member.auth.provided.dto 패키지 위치도 포트 기반 아키텍처에 부합하며, 정적 팩토리 메서드를 통한 변환 패턴도 깔끔합니다.

선택적 개선 사항: phoneNumber 필드가 PII(개인식별정보)에 해당하므로, 인증 컨텍스트에서 실제로 필요한지 검토해보시기 바랍니다. 불필요하게 노출되는 개인정보를 최소화하는 것이 GDPR/CCPA 관점에서 바람직합니다.

src/main/java/starlight/application/member/provided/MemberQueryUseCase.java (2)

6-6: Use Case 네이밍이 개선되었지만 역할 분리를 고려해보세요.

MemberServiceMemberQueryUseCase로 변경하여 유스케이스 인터페이스임을 명확히 했습니다. 이는 아키텍처 의도를 잘 드러냅니다.

다만 QueryUseCasecreateUser 같은 커맨드 오퍼레이션이 포함되어 있어 CQRS 관점에서 일관성이 떨어집니다. 다음과 같은 분리를 고려해보시기 바랍니다:

  • MemberQueryUseCase: getUserByEmail, getUserById (조회 전용)
  • MemberCommandUseCase: createUser (변경 작업)

이미 포트 계층에서 MemberCommandPortMemberQueryPort가 분리되어 있다면, 유스케이스 레벨에서도 동일한 분리 원칙을 적용하는 것이 일관성 있는 설계입니다.


8-8: 파라미터 분리가 개선되었으나 Input DTO 활용을 권장합니다.

AuthRequest(웹 계층 DTO)를 직접 받지 않고 개별 파라미터로 분리한 것은 계층 간 결합도를 낮추는 좋은 변경입니다.

다만 PR 목표가 Input/Result DTO 패턴을 도입하는 것이고, 이미 SignUpInput이 존재하는 것으로 보이므로, 개별 파라미터 4개 대신 SignUpInput을 받는 것이 더 나을 수 있습니다:

Member createUser(Credential credential, SignUpInput input);

이렇게 하면:

  • 파라미터 개수 감소로 메서드 시그니처가 간결해집니다
  • 향후 필드 추가 시 시그니처 변경이 불필요합니다
  • Input DTO 네이밍 컨벤션과 일관성이 유지됩니다
src/main/java/starlight/adapter/member/auth/security/handler/JwtAccessDeniedHandler.java (1)

23-29: 중복 코드 추출을 고려해볼 수 있습니다.

JwtAuthenticationHandler, JwtAccessDeniedHandler, ExceptionFilter 세 핸들러가 유사한 JSON 응답 작성 로직을 공유합니다. 공통 유틸리티 메서드로 추출하면 유지보수성이 향상될 수 있습니다.

다만 각 핸들러가 독립적으로 동작하는 것도 장점이므로, 필수는 아닙니다.

🔎 추출 가능한 공통 로직
public class SecurityResponseWriter {
    public static void writeErrorResponse(
        HttpServletResponse response,
        ObjectMapper objectMapper,
        int status,
        GlobalErrorType errorType
    ) throws IOException {
        if (response.isCommitted()) {
            return;
        }
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(status);
        ApiResponse<?> errorResponse = ApiResponse.error(errorType);
        response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
    }
}
src/main/java/starlight/application/member/required/MemberQueryPort.java (1)

13-15: 중복 가능성 검토 제안

findByProviderAndProviderIdfindByProviderAndProviderIdOrThrow가 공존하는 구조입니다. 사용 빈도가 낮은 경우, default method를 활용하여 중복을 줄일 수 있습니다:

default Member findByProviderAndProviderIdOrThrow(String provider, String providerId) {
    return findByProviderAndProviderId(provider, providerId)
        .orElseThrow(() -> new MemberException(...));
}

단, 예외 처리 로직이 복잡하거나 adapter에서 최적화가 필요한 경우 현재 구조가 더 적합할 수 있습니다.

개발가이드.md (1)

45-50: 네이밍 예시 추가 제안

실패 동작 네이밍 규칙이 잘 정리되어 있습니다. 다만 부정적인 예시(anti-pattern)도 함께 제시하면 더 명확할 수 있습니다:

- 실패 동작을 이름으로 드러낸다.
  - Optional/nullable 반환: `findById`, `findByEmail`
  - 예외를 던짐: `findByIdOrThrow`, `findByTokenOrThrow`
  - 연관 강제 로딩: `findByIdWithCareersAndTags`, `fetchExpertsWithTagsByIds`
  - ❌ 피해야 할 패턴: `getOrThrow`, `getMember` (조회 동작은 find*로 통일)
src/main/java/starlight/application/expertApplication/event/FeedbackRequestEventListener.java (1)

43-52: 실패 처리 전략 구현을 고려하세요.

재시도가 모두 실패한 경우의 복구 로직이 TODO로 남아있습니다. TODO에 언급된 세 가지 전략(DLQ 저장, 관리자 알림, DB 실패 기록) 중 최소한 하나는 구현하는 것이 프로덕션 안정성을 위해 권장됩니다.

실패 처리 전략 구현을 도와드릴까요? 예를 들어 실패 이벤트를 DB에 기록하는 간단한 구현부터 시작할 수 있습니다.

src/main/java/starlight/adapter/expertReport/persistence/ExpertReportRepository.java (1)

16-24: 명시적 @query 어노테이션이 불필요할 수 있습니다.

현재 JPQL 쿼리는 Spring Data JPA의 메서드 이름 기반 쿼리 파생으로 자동 생성 가능합니다. 예를 들어 findByTokenfindAllByBusinessPlanIdOrderByCreatedAtDesc 메서드 이름만으로도 동일한 쿼리가 생성됩니다.

명시적 @query를 사용하는 특별한 이유(성능 튜닝, 커스텀 조인 전략 등)가 없다면, 유지보수를 위해 메서드 이름 기반 쿼리로 단순화하는 것을 고려해보세요. 다만 "WithComments" 접미사를 통해 EntityGraph 사용을 명시하려는 의도라면 현재 구현도 합리적입니다.

🔎 메서드 이름 기반 쿼리로 단순화하는 경우
     @EntityGraph(attributePaths = {"comments"})
-    @Query("select er from ExpertReport er where er.token = :token")
-    Optional<ExpertReport> findByTokenWithComments(@Param("token") String token);
+    Optional<ExpertReport> findByTokenWithComments(String token);
 
     @EntityGraph(attributePaths = {"comments"})
-    @Query("select er from ExpertReport er where er.businessPlanId = :businessPlanId order by er.createdAt desc")
-    List<ExpertReport> findAllByBusinessPlanIdWithCommentsOrderByCreatedAtDesc(
-            @Param("businessPlanId") Long businessPlanId
-    );
+    List<ExpertReport> findAllByBusinessPlanIdWithCommentsOrderByCreatedAtDesc(Long businessPlanId);
src/main/java/starlight/application/member/MemberQueryService.java (1)

21-33: Command 작업이 Query Service에 포함되어 있습니다.

createUser 메서드는 데이터를 생성하고 저장하는 Command 작업입니다. 현재 클래스명이 MemberQueryService이므로 CQRS 원칙상 읽기 전용 작업만 포함해야 합니다.

createUser 메서드를 별도의 MemberCommandService로 이동하거나, 서비스 이름을 MemberService로 변경하여 Query/Command를 모두 처리하는 서비스임을 명확히 하는 것을 권장합니다.

🔎 MemberCommandService로 분리하는 예시

MemberCommandService.java를 새로 생성:

@Service
@RequiredArgsConstructor
public class MemberCommandService implements MemberCommandUseCase {
    
    private final MemberQueryPort memberQueryPort;
    private final MemberCommandPort memberCommandPort;
    
    public Member createUser(Credential credential, String name, String email, String phoneNumber) {
        memberQueryPort.findByEmail(email).ifPresent(existingUser -> {
            throw new MemberException(MemberErrorType.MEMBER_ALREADY_EXISTS);
        });
        Member member = Member.create(name, email, phoneNumber, MemberType.FOUNDER, credential, null);
        return memberCommandPort.save(member);
    }
}

그리고 MemberQueryService에서는 제거:

 @Service
 @RequiredArgsConstructor
 public class MemberQueryService implements MemberQueryUseCase {
 
-    private final MemberQueryPort memberQueryPort;
-    private final MemberCommandPort memberCommandPort;
+    private final MemberQueryPort memberQueryPort;
 
-    public Member createUser(Credential credential, String name, String email, String phoneNumber) {
-        memberQueryPort.findByEmail(email).ifPresent(existingUser -> {
-            throw new MemberException(MemberErrorType.MEMBER_ALREADY_EXISTS);
-        });
-        Member member = Member.create(name, email, phoneNumber, MemberType.FOUNDER, credential, null);
-        return memberCommandPort.save(member);
-    }
-
     public Member getUserByEmail(String email) {
         return memberQueryPort.findByEmail(email)
                 .orElseThrow(() -> new MemberException(MemberErrorType.MEMBER_NOT_FOUND));
     }
src/main/java/starlight/adapter/member/auth/webapi/AuthTokenResolver.java (3)

27-45: 토큰 추출 로직이 견고하게 구현되었습니다.

extractTokennormalizePrefix 메서드 모두 null/blank 값과 엣지 케이스를 적절히 처리하고 있습니다.

Line 32에서 전체 문자열을 소문자로 변환한 후 prefix를 체크하는 것은 안전하지만, 약간의 성능 개선 여지가 있습니다. 하지만 이는 nitpick 수준이며 현재 구현도 충분히 명확합니다.

🔎 미세 최적화 제안 (선택적)

전체 문자열 변환 대신 regionMatches를 사용할 수 있습니다:

 private String extractToken(String raw) {
     if (raw == null) {
         return null;
     }
     String trimmed = raw.trim();
-    if (trimmed.toLowerCase().startsWith(bearerPrefix)) {
+    if (trimmed.regionMatches(true, 0, bearerPrefix, 0, bearerPrefix.length())) {
         String token = trimmed.substring(bearerPrefix.length()).trim();
         return token.isEmpty() ? null : token;
     }
     return trimmed.isEmpty() ? null : trimmed;
 }

하지만 가독성 측면에서 현재 구현이 더 명확하므로, 성능이 병목이 되지 않는 한 현재대로 유지하는 것을 권장합니다.


19-25: 메서드 중복 제거를 위해 공통 로직을 private 메서드로 추출하세요.

resolveAccessTokenresolveRefreshToken이 완전히 동일한 구현을 가지고 있습니다. 현재 코드는 같은 HTTP 헤더(authHeaderName)에서 토큰을 추출하며, 이는 프로젝트의 인증 설계에 따른 의도적 선택으로 보입니다. 토큰들은 HTTP 헤더 위치가 아닌 JWT 페이로드 내용으로 구분됩니다.

코드 중복을 제거하기 위해 resolveToken(HttpServletRequest request) 같은 private 메서드를 만들고 두 public 메서드에서 호출하는 방식을 권장합니다.

예시
 public String resolveAccessToken(HttpServletRequest request) {
-    return extractToken(request.getHeader(authHeaderName));
+    return resolveToken(request);
 }
 
 public String resolveRefreshToken(HttpServletRequest request) {
-    return extractToken(request.getHeader(authHeaderName));
+    return resolveToken(request);
+}
+
+private String resolveToken(HttpServletRequest request) {
+    return extractToken(request.getHeader(authHeaderName));
 }

1-46: Deprecated 메서드 선언을 정리하여 리팩토링을 완료하세요.

좋은 책임 분리입니다. AuthTokenResolver로의 마이그레이션이 프로덕션 코드에서 완료되었으며, AuthControllerJwtFilter 모두 새 구현을 사용하고 있습니다.

다음 단계로 리팩토링을 완료하세요:

  • TokenProvider 인터페이스에서 deprecated된 resolveAccessToken(), resolveRefreshToken() 메서드 선언 제거
  • JwtTokenProvider 클래스에서 deprecated 메서드 구현 제거
  • JwtTokenProviderTest에서 deprecated 메서드를 직접 테스트하는 코드 정리

normalizePrefix 메서드는 두 클래스에 각각 존재하지만, 내부 유틸리티로 사용되므로 현재 상태로도 무방합니다.

src/test/java/starlight/application/businessplan/BusinessPlanServiceImplIntegrationTest.java (1)

52-79: MemberQueryPort 테스트 구현이 적절합니다.

포트 기반 아키텍처로의 전환에 맞춰 테스트 빈 설정이 올바르게 업데이트되었습니다. 새로운 메서드들(findByEmail, findByProviderAndProviderId, findByProviderAndProviderIdOrThrow)이 인터페이스 요구사항에 맞게 구현되어 있습니다.

다만, 테스트 클래스 내에서 java.util.Optional을 직접 fully-qualified로 사용하고 있는데, 상단에 import를 추가하면 더 깔끔해집니다.

🔎 제안: Optional import 추가
 import starlight.application.member.required.MemberQueryPort;
 import starlight.domain.businessplan.entity.BusinessPlan;
 import starlight.domain.businessplan.entity.SubSection;
 import starlight.domain.businessplan.enumerate.SubSectionType;
 import starlight.domain.member.entity.Member;
 
 import java.util.List;
+import java.util.Optional;

그리고 메서드 내에서:

 @Override
-public java.util.Optional<Member> findByEmail(String email) {
-    return java.util.Optional.empty();
+public Optional<Member> findByEmail(String email) {
+    return Optional.empty();
 }
src/main/java/starlight/application/expertReport/ExpertReportService.java (1)

100-121: 리스트 조회 메서드 리팩토링 확인

  • 반환 타입이 List<ExpertReportWithExpertResult>로 변경
  • businessPlanQuery.findByIdOrThrow(businessPlanId) 호출 후 결과를 사용하지 않음 - 존재 여부 검증용으로 보이나, 명시적인 주석이 있으면 의도가 더 명확해집니다.
🔎 의도 명확화를 위한 제안
 @Transactional(readOnly = true)
 public List<ExpertReportWithExpertResult> getExpertReportsWithExpertByBusinessPlanId(Long businessPlanId) {
+    // 사업계획서 존재 여부 검증
     businessPlanQuery.findByIdOrThrow(businessPlanId);
src/main/java/starlight/adapter/order/toss/TossClient.java (1)

18-51: PaymentGatewayPort 구현 및 에러 핸들링

포트 기반 아키텍처가 잘 적용되었습니다. 다만 catch (Exception e) 블록이 모든 예외를 포괄적으로 처리하고 있어, 특정 예외 유형(예: RestClientException, IllegalStateException)에 따른 세분화된 처리가 필요할 수 있습니다.

현재 구현에서는 validation 실패도 TOSS_CLIENT_CONFIRM_ERROR로 래핑되어 디버깅이 어려울 수 있습니다.

🔎 예외 처리 세분화 제안
         } catch (Exception e) {
             log.error("토스 결제 승인 요청 중 에러 발생. orderId: {}, paymentKey: {}, message: {}", orderCode, paymentKey, e.getMessage(), e);
+            if (e instanceof IllegalStateException) {
+                throw new OrderException(OrderErrorType.TOSS_CLIENT_VALIDATION_ERROR);
+            }
             throw new OrderException(OrderErrorType.TOSS_CLIENT_CONFIRM_ERROR);
         }
src/main/java/starlight/adapter/member/auth/security/oauth2/CustomOAuth2UserService.java (1)

42-54: orElseGet 내부에서 부작용(save)을 수행하는 것은 권장되지 않습니다.

orElseGet은 값의 지연 생성을 위한 것이지, 부작용(side effect)을 수행하기 위한 것이 아닙니다. 코드 가독성과 의도 명확성을 위해 분리하는 것을 고려해 주세요.

🔎 제안하는 리팩토링
-        Member member = found.orElseGet(() ->
-                memberCommandPort.save(
-                        Member.newSocial(
-                                parsed.name(),
-                                parsed.email(),
-                                parsed.provider(),
-                                parsed.providerId(),
-                                null,
-                                MemberType.FOUNDER,
-                                parsed.profileImageUrl()
-                        )
-                )
-        );
+        Member member;
+        if (found.isPresent()) {
+            member = found.get();
+        } else {
+            Member newMember = Member.newSocial(
+                    parsed.name(),
+                    parsed.email(),
+                    parsed.provider(),
+                    parsed.providerId(),
+                    null,
+                    MemberType.FOUNDER,
+                    parsed.profileImageUrl()
+            );
+            member = memberCommandPort.save(newMember);
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d98e537 and 1ca5120.

📒 Files selected for processing (109)
  • src/main/java/starlight/adapter/aireport/webapi/AiReportController.java
  • src/main/java/starlight/adapter/aireport/webapi/ImageController.java
  • src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java
  • src/main/java/starlight/adapter/auth/security/handler/JwtAccessDeniedHandler.java
  • src/main/java/starlight/adapter/auth/security/handler/JwtAuthenticationHandler.java
  • src/main/java/starlight/adapter/auth/security/jwt/dto/TokenResponse.java
  • src/main/java/starlight/adapter/auth/webapi/AuthController.java
  • src/main/java/starlight/adapter/auth/webapi/swagger/AuthApiDoc.java
  • src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java
  • src/main/java/starlight/adapter/businessplan/webapi/BusinessPlanController.java
  • src/main/java/starlight/adapter/expert/persistence/ExpertJpa.java
  • src/main/java/starlight/adapter/expertApplication/email/SMTPEmailSender.java
  • src/main/java/starlight/adapter/expertApplication/webapi/ExpertApplicationController.java
  • src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java
  • src/main/java/starlight/adapter/expertReport/persistence/ExpertReportJpa.java
  • src/main/java/starlight/adapter/expertReport/persistence/ExpertReportRepository.java
  • src/main/java/starlight/adapter/expertReport/webapi/ExpertReportController.java
  • src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportCareerResponse.java
  • src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportExpertResponse.java
  • src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportResponse.java
  • src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java
  • src/main/java/starlight/adapter/member/auth/security/auth/AuthDetails.java
  • src/main/java/starlight/adapter/member/auth/security/auth/AuthDetailsService.java
  • src/main/java/starlight/adapter/member/auth/security/filter/ExceptionFilter.java
  • src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java
  • src/main/java/starlight/adapter/member/auth/security/handler/JwtAccessDeniedHandler.java
  • src/main/java/starlight/adapter/member/auth/security/handler/JwtAuthenticationHandler.java
  • src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java
  • src/main/java/starlight/adapter/member/auth/security/jwt/dto/TokenResponse.java
  • src/main/java/starlight/adapter/member/auth/security/oauth2/CustomOAuth2UserService.java
  • src/main/java/starlight/adapter/member/auth/security/oauth2/OAuth2Attributes.java
  • src/main/java/starlight/adapter/member/auth/security/oauth2/OAuth2SuccessHandler.java
  • src/main/java/starlight/adapter/member/auth/webapi/AuthController.java
  • src/main/java/starlight/adapter/member/auth/webapi/AuthTokenResolver.java
  • src/main/java/starlight/adapter/member/auth/webapi/dto/request/AuthRequest.java
  • src/main/java/starlight/adapter/member/auth/webapi/dto/request/SignInRequest.java
  • src/main/java/starlight/adapter/member/auth/webapi/dto/response/MemberResponse.java
  • src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java
  • src/main/java/starlight/adapter/member/persistence/CredentialRepository.java
  • src/main/java/starlight/adapter/member/persistence/MemberJpa.java
  • src/main/java/starlight/adapter/member/webapi/MemberController.java
  • src/main/java/starlight/adapter/member/webapi/dto/MemberDetailResponse.java
  • src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java
  • src/main/java/starlight/adapter/order/toss/TossClient.java
  • src/main/java/starlight/adapter/order/webapi/OrderController.java
  • src/main/java/starlight/adapter/order/webapi/dto/response/OrderCancelResponse.java
  • src/main/java/starlight/adapter/order/webapi/swagger/OrderApiDoc.java
  • src/main/java/starlight/application/aireport/AiReportServiceImpl.java
  • src/main/java/starlight/application/auth/AuthServiceImpl.java
  • src/main/java/starlight/application/auth/provided/AuthService.java
  • src/main/java/starlight/application/auth/required/TokenProvider.java
  • src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java
  • src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java
  • src/main/java/starlight/application/expertApplication/ExpertApplicationCommandService.java
  • src/main/java/starlight/application/expertApplication/ExpertApplicationQueryService.java
  • src/main/java/starlight/application/expertApplication/event/FeedbackRequestEventListener.java
  • src/main/java/starlight/application/expertApplication/event/FeedbackRequestInput.java
  • src/main/java/starlight/application/expertApplication/required/EmailSender.java
  • src/main/java/starlight/application/expertApplication/required/ExpertLookupPort.java
  • src/main/java/starlight/application/expertReport/ExpertReportService.java
  • src/main/java/starlight/application/expertReport/provided/ExpertReportServiceUseCase.java
  • src/main/java/starlight/application/expertReport/provided/dto/ExpertReportWithExpertResult.java
  • src/main/java/starlight/application/expertReport/required/ExpertReportQueryPort.java
  • src/main/java/starlight/application/member/CredentialServiceImpl.java
  • src/main/java/starlight/application/member/MemberQueryService.java
  • src/main/java/starlight/application/member/MemberServiceImpl.java
  • src/main/java/starlight/application/member/auth/AuthServiceImpl.java
  • src/main/java/starlight/application/member/auth/provided/AuthUseCase.java
  • src/main/java/starlight/application/member/auth/provided/dto/AuthMemberResult.java
  • src/main/java/starlight/application/member/auth/provided/dto/AuthTokenResult.java
  • src/main/java/starlight/application/member/auth/provided/dto/SignInInput.java
  • src/main/java/starlight/application/member/auth/provided/dto/SignUpInput.java
  • src/main/java/starlight/application/member/auth/required/KeyValueMap.java
  • src/main/java/starlight/application/member/auth/required/TokenProvider.java
  • src/main/java/starlight/application/member/provided/CredentialService.java
  • src/main/java/starlight/application/member/provided/MemberQueryUseCase.java
  • src/main/java/starlight/application/member/required/MemberCommandPort.java
  • src/main/java/starlight/application/member/required/MemberQueryPort.java
  • src/main/java/starlight/application/order/OrderPaymentService.java
  • src/main/java/starlight/application/order/provided/OrderPaymentServiceUseCase.java
  • src/main/java/starlight/application/order/provided/dto/PaymentHistoryItemResult.java
  • src/main/java/starlight/application/order/provided/dto/TossClientResult.java
  • src/main/java/starlight/application/order/required/PaymentGatewayPort.java
  • src/main/java/starlight/bootstrap/SecurityConfig.java
  • src/main/java/starlight/domain/member/auth/exception/AuthErrorType.java
  • src/main/java/starlight/domain/member/auth/exception/AuthException.java
  • src/main/java/starlight/shared/auth/AuthenticatedMember.java
  • src/test/java/starlight/adapter/aireport/infrastructure/webapi/ImageControllerIntegrationTest.java
  • src/test/java/starlight/adapter/expert/webapi/ExpertControllerTest.java
  • src/test/java/starlight/adapter/member/auth/redis/RedisKeyValueMapTest.java
  • src/test/java/starlight/adapter/member/auth/security/filter/ExceptionFilterUnitTest.java
  • src/test/java/starlight/adapter/member/auth/security/jwt/JwtTokenProviderTest.java
  • src/test/java/starlight/adapter/member/auth/security/oauth2/CustomOAuth2UserServiceUnitTest.java
  • src/test/java/starlight/adapter/member/auth/security/oauth2/OAuth2AttributesUnitTest.java
  • src/test/java/starlight/adapter/member/auth/security/oauth2/OAuth2LoginFlowTest.java
  • src/test/java/starlight/adapter/member/auth/security/oauth2/OAuth2SuccessHandlerUnitTest.java
  • src/test/java/starlight/adapter/member/auth/security/oauth2/TestOAuth2Objects.java
  • src/test/java/starlight/adapter/member/auth/webapi/AuthControllerSliceTest.java
  • src/test/java/starlight/application/aireport/AiReportServiceImplUnitTest.java
  • src/test/java/starlight/application/businessplan/BusinessPlanServiceImplIntegrationTest.java
  • src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java
  • src/test/java/starlight/application/member/CredentialServiceImplIntegrationTest.java
  • src/test/java/starlight/application/member/CredentialServiceImplUnitTest.java
  • src/test/java/starlight/application/member/MemberQueryServiceIntegrationTest.java
  • src/test/java/starlight/application/member/MemberQueryServiceUnitTest.java
  • src/test/java/starlight/application/member/auth/AuthServiceImplIntegrationTest.java
  • src/test/java/starlight/application/member/auth/AuthServiceImplUnitTest.java
  • src/test/java/starlight/bootstrap/SecurityConfigTest.java
  • 개발가이드.md
💤 Files with no reviewable changes (10)
  • src/main/java/starlight/application/auth/required/TokenProvider.java
  • src/main/java/starlight/adapter/auth/webapi/swagger/AuthApiDoc.java
  • src/main/java/starlight/adapter/auth/security/handler/JwtAuthenticationHandler.java
  • src/main/java/starlight/adapter/auth/webapi/AuthController.java
  • src/main/java/starlight/application/auth/AuthServiceImpl.java
  • src/main/java/starlight/application/auth/provided/AuthService.java
  • src/main/java/starlight/adapter/member/persistence/CredentialRepository.java
  • src/main/java/starlight/adapter/auth/security/handler/JwtAccessDeniedHandler.java
  • src/main/java/starlight/adapter/auth/security/jwt/dto/TokenResponse.java
  • src/main/java/starlight/application/member/MemberServiceImpl.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-26T15:38:55.886Z
Learnt from: SeongHo5356
Repo: StartUpLight/STARLIGHT_BE PR: 67
File: src/main/java/starlight/adapter/expert/persistence/ExpertJpa.java:36-51
Timestamp: 2025-12-26T15:38:55.886Z
Learning: StarLight의 Expert 조회에서 `findByIdWithCareersAndTags()` 메서드는 단건 상세 조회용으로, ExpertDetailResponse가 categories 필드를 포함하지 않기 때문에 의도적으로 categories 페치를 제외합니다. ExpertListResponse만 categories를 포함합니다.

Applied to files:

  • src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportExpertResponse.java
🧬 Code graph analysis (15)
src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java (1)
src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java (1)
  • Schema (306-324)
src/main/java/starlight/application/member/MemberQueryService.java (1)
src/main/java/starlight/domain/member/exception/MemberException.java (1)
  • MemberException (6-11)
src/main/java/starlight/application/member/auth/AuthServiceImpl.java (5)
src/main/java/starlight/application/member/MemberQueryService.java (1)
  • Service (14-49)
src/main/java/starlight/adapter/member/auth/security/auth/AuthDetailsService.java (1)
  • Service (14-29)
src/main/java/starlight/application/member/CredentialServiceImpl.java (1)
  • Service (12-41)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
src/main/java/starlight/domain/member/exception/MemberException.java (1)
  • MemberException (6-11)
src/main/java/starlight/adapter/member/persistence/MemberJpa.java (2)
src/main/java/starlight/domain/member/exception/MemberException.java (1)
  • MemberException (6-11)
src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java (1)
  • Repository (12-39)
src/main/java/starlight/adapter/member/auth/security/jwt/dto/TokenResponse.java (1)
src/main/java/starlight/adapter/auth/security/jwt/dto/TokenResponse.java (1)
  • TokenResponse (3-11)
src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportExpertResponse.java (1)
src/main/java/starlight/adapter/expert/webapi/dto/ExpertListResponse.java (1)
  • ExpertListResponse (8-57)
src/test/java/starlight/application/member/CredentialServiceImplUnitTest.java (1)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
src/main/java/starlight/adapter/member/auth/security/handler/JwtAuthenticationHandler.java (6)
src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java (1)
  • Component (25-227)
src/main/java/starlight/adapter/member/auth/security/handler/JwtAccessDeniedHandler.java (1)
  • Component (15-31)
src/main/java/starlight/adapter/member/auth/webapi/AuthTokenResolver.java (1)
  • Component (7-46)
src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java (1)
  • Component (13-79)
src/main/java/starlight/adapter/member/auth/security/filter/ExceptionFilter.java (1)
  • Component (17-49)
src/main/java/starlight/adapter/auth/security/handler/JwtAuthenticationHandler.java (1)
  • Component (12-20)
src/test/java/starlight/application/member/CredentialServiceImplIntegrationTest.java (1)
src/test/java/starlight/application/member/CredentialServiceImplUnitTest.java (1)
  • ExtendWith (16-53)
src/main/java/starlight/adapter/member/auth/security/auth/AuthDetailsService.java (3)
src/main/java/starlight/application/member/auth/AuthServiceImpl.java (1)
  • Service (23-147)
src/main/java/starlight/adapter/member/auth/security/oauth2/CustomOAuth2UserService.java (1)
  • Service (18-65)
src/main/java/starlight/application/member/CredentialServiceImpl.java (1)
  • Service (12-41)
src/main/java/starlight/application/member/CredentialServiceImpl.java (1)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
src/test/java/starlight/adapter/member/auth/webapi/AuthControllerSliceTest.java (2)
src/test/java/starlight/bootstrap/SecurityConfigTest.java (1)
  • WebMvcTest (22-44)
src/test/java/starlight/adapter/member/auth/security/oauth2/OAuth2LoginFlowTest.java (1)
  • TestConfiguration (61-80)
src/test/java/starlight/application/member/auth/AuthServiceImplUnitTest.java (1)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
src/main/java/starlight/adapter/member/webapi/MemberController.java (3)
src/main/java/starlight/adapter/member/auth/security/oauth2/OAuth2SuccessHandler.java (1)
  • Slf4j (20-51)
src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java (1)
  • Slf4j (22-50)
src/main/java/starlight/bootstrap/SecurityConfig.java (1)
  • Slf4j (34-127)
src/test/java/starlight/application/member/auth/AuthServiceImplIntegrationTest.java (1)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
🪛 Gitleaks (8.30.0)
src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java

[high] 113-113: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 114-114: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 266-266: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 267-267: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

@github-actions
Copy link

github-actions bot commented Dec 31, 2025

Test Results

248 tests   248 ✅  10s ⏱️
 46 suites    0 💤
 46 files      0 ❌

Results for commit 9eeae3b.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java (1)

26-30: 네이밍 일관성을 위해 메서드명 변경을 권장합니다.

Line 19에서 getOrThrowfindByIdOrThrow로 변경했지만, 이 메서드는 여전히 get 접두사를 사용하고 있습니다. 동일한 클래스 내에서 유사한 목적(필수 조회 + 예외 발생)을 가진 메서드들의 네이밍을 통일하는 것이 좋습니다.

findByIdWithAllSubSectionsOrThrow 또는 findWithAllSubSectionsByIdOrThrow와 같이 find 접두사로 시작하도록 변경을 고려해보세요.

src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java (1)

11-11: 인터페이스 전체의 네이밍 일관성을 위한 변경을 권장합니다.

Line 9의 메서드는 find 접두사로 변경되었지만, 이 메서드는 여전히 get 접두사를 사용하고 있습니다. 포트 인터페이스 내의 모든 조회 메서드가 동일한 네이밍 패턴을 따르도록 findByIdWithAllSubSectionsOrThrow 형태로 변경하는 것을 고려해보세요.

이 변경은 구현체인 BusinessPlanJpa 클래스에도 동일하게 적용되어야 합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca5120 and 6d64080.

📒 Files selected for processing (3)
  • src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java
  • src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java
  • src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-and-push
  • GitHub Check: test
🔇 Additional comments (4)
src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java (1)

19-23: 메서드명 변경이 적절합니다!

getOrThrow에서 findByIdOrThrow로의 변경은 Spring Data JPA의 네이밍 컨벤션과 잘 맞으며, 메서드의 의도를 더 명확하게 표현합니다. 'find' 접두사는 조회 작업을, 'ById'는 조회 기준을, 'OrThrow'는 실패 시 예외 발생을 명시적으로 나타냅니다.

src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java (1)

9-9: 인터페이스 메서드명 변경이 적절합니다!

포트 인터페이스의 메서드명을 findByIdOrThrow로 변경한 것은 헥사고날 아키텍처에서 명확한 계약을 정의하며, 구현체들과의 일관성을 보장합니다.

src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java (2)

19-19: 포트 네이밍 규칙이 잘 적용되었습니다!

MemberQuery에서 MemberQueryPort로의 변경은 헥사고날 아키텍처의 포트 명명 규칙을 명확하게 따르고 있습니다. 'Port' 접미사는 이 인터페이스가 애플리케이션 계층의 포트임을 명시적으로 나타내며, 어댑터와의 경계를 분명하게 합니다.

Also applies to: 38-38


44-44: 메서드 호출이 올바르게 업데이트되었습니다!

두 곳 모두에서 findByIdOrThrow 메서드 호출로 일관되게 변경되었습니다. 서비스 레이어 전반에 걸쳐 새로운 네이밍 컨벤션이 적절하게 적용되어 코드의 가독성과 일관성이 향상되었습니다.

Also applies to: 239-239

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/starlight/application/member/auth/AuthServiceImpl.java (1)

74-93: 로그아웃 로직은 정확하나, 코드 중복을 제거하면 더 깔끔해집니다.

refreshToken과 accessToken에 대한 검증 로직이 동일하게 반복되고 있습니다(lines 86-91). 헬퍼 메서드를 추가하거나 단일 조건문으로 통합하면 가독성이 향상됩니다.

🔎 중복 제거 제안
     @Override
     @Transactional
     public void signOut(String refreshToken, String accessToken) {
         if (refreshToken == null || accessToken == null) {
             throw new AuthException(AuthErrorType.TOKEN_NOT_FOUND);
         }
-        if (!tokenProvider.validateToken(accessToken)) {
-            throw new AuthException(AuthErrorType.TOKEN_INVALID);
-        }
-        if (!tokenProvider.validateToken(refreshToken)) {
+        if (!tokenProvider.validateToken(accessToken) || !tokenProvider.validateToken(refreshToken)) {
             throw new AuthException(AuthErrorType.TOKEN_INVALID);
         }
         tokenProvider.logoutTokens(refreshToken, accessToken);
     }
src/main/java/starlight/application/member/CredentialServiceImpl.java (1)

18-27: 과거 리뷰 피드백이 완벽하게 반영되었습니다! 우수한 리팩토링입니다.

이전 리뷰에서 지적된 Javadoc과 실제 동작의 불일치 문제가 완전히 해결되었습니다:

  • Javadoc이 "생성하는 메서드"로 정확하게 수정됨
  • "@return 저장되지 않은 Credential"을 명시하여 메서드의 책임 범위를 명확히 함
  • 저장 로직을 제거하여 단일 책임 원칙(SRP)을 준수
  • AuthRequest 대신 String rawPassword를 직접 받아 결합도 감소

이는 헥사고날 아키텍처에서 application 서비스가 비즈니스 로직(암호화)만 담당하고, 영속성 책임은 상위 유스케이스로 분리하는 올바른 설계입니다.


선택적 개선 사항: 입력 검증 추가 고려

rawPassword에 대한 null 또는 빈 문자열 검증을 추가하면 방어적 프로그래밍을 강화할 수 있습니다. 다만 상위 레이어(컨트롤러/유스케이스)에서 이미 검증하고 있다면 불필요할 수 있습니다.

🔎 선택적 개선안
 public Credential createCredential(String rawPassword) {
+    if (rawPassword == null || rawPassword.isBlank()) {
+        throw new AuthException(AuthErrorType.INVALID_PASSWORD);
+    }
     String hashedPassword = passwordEncoder.encode(rawPassword);
     return Credential.create(hashedPassword);
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d64080 and d46f64c.

📒 Files selected for processing (5)
  • config
  • src/main/java/starlight/application/member/CredentialServiceImpl.java
  • src/main/java/starlight/application/member/auth/AuthServiceImpl.java
  • src/test/java/starlight/adapter/member/auth/security/jwt/JwtTokenProviderTest.java
  • src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java
✅ Files skipped from review due to trivial changes (1)
  • config
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java
  • src/test/java/starlight/adapter/member/auth/security/jwt/JwtTokenProviderTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/starlight/application/member/CredentialServiceImpl.java (1)
src/main/java/starlight/domain/member/auth/exception/AuthException.java (1)
  • AuthException (6-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-and-push
🔇 Additional comments (7)
src/main/java/starlight/application/member/auth/AuthServiceImpl.java (5)

23-34: 클래스 구조와 의존성 설정이 우수합니다.

헥사고날 아키텍처의 포트/어댑터 패턴이 잘 적용되었고, 생성자 주입 방식으로 의존성을 관리하고 있습니다. 토큰 만료 시간을 외부 설정으로 관리하는 점도 좋습니다.


36-54: 이전 리뷰 피드백이 반영되었습니다. 잘 구현되었습니다.

Javadoc의 파라미터명이 실제 메서드 시그니처(input)와 일치하도록 수정되었고, 회원가입 로직이 명확하게 구현되어 있습니다. Credential 생성 → Member 생성 → Result 반환 흐름이 깔끔합니다.


56-72: 이전 리뷰 피드백이 반영되었습니다. 로그인 로직이 잘 구현되었습니다.

Javadoc 파라미터명이 수정되었고, 로그인 시 Redis에 refresh token을 만료 시간과 함께 저장하는 로직이 정확하게 구현되어 있습니다.


95-131: 이전 리뷰 피드백이 반영되었습니다. 토큰 재발급 로직이 철저하게 구현되었습니다.

Javadoc 파라미터명(memberId)이 수정되었고, 다층적인 검증 로직(토큰 유효성, 이메일 일치, Redis 저장값 비교)이 잘 구현되어 있습니다. 보안을 고려한 방어적 프로그래밍이 돋보입니다.

참고: Line 125의 refreshToken.isEmpty() 체크는 extractToken 메서드가 이미 빈 토큰을 거부하므로 기술적으로 불필요하지만, 명시적 방어 코드로서 유지해도 무방합니다.


133-146: 토큰 추출 헬퍼 메서드가 잘 구현되었습니다.

Bearer 접두어 처리와 공백 제거, 빈 토큰 검증 등 엣지 케이스를 모두 고려한 방어적 프로그래밍이 적용되어 있습니다.

src/main/java/starlight/application/member/CredentialServiceImpl.java (2)

7-8: 도메인 재배치가 올바르게 반영되었습니다.

인증 관련 예외를 starlight.domain.member.auth.exception 패키지로 이동한 것은 PR의 목표인 도메인 책임 재배치와 일치하며, 헥사고날 아키텍처에서 application 레이어가 도메인 예외를 사용하는 것은 적절한 의존성 방향입니다.


34-40: 변경사항 없음 - 로직 정상 확인

checkPassword 메서드는 이번 리팩토링에서 변경되지 않았으며, 비밀번호 검증 로직이 정확하게 구현되어 있습니다. AuthException 사용도 업데이트된 import 경로와 일관됩니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportResponse.java (1)

21-31: LGTM! 이전 리뷰 피드백이 반영되었습니다.

fromEntities() 메서드가 이제 applicationCount를 파라미터로 받아 ExpertDetailResult.from(expert, applicationCount)에 전달합니다. 이전에 하드코딩되어 있던 0L 문제가 해결되었습니다.

🧹 Nitpick comments (1)
src/main/java/starlight/application/expert/ExpertDetailQueryService.java (1)

21-21: LGTM! 새 포트 타입으로 정상 변경되었습니다.

타입 변경이 올바르게 적용되었습니다. 필드명도 expertApplicationCountLookupPort로 맞추면 타입과의 일관성이 높아지지만, 현재 상태로도 기능상 문제는 없습니다.

🔎 선택적 네이밍 개선
-    private final ExpertApplicationCountLookupPort expertApplicationLookupPort;
+    private final ExpertApplicationCountLookupPort expertApplicationCountLookupPort;

사용하는 곳도 함께 변경 필요:

-        Map<Long, Long> countMap = expertApplicationLookupPort.countByExpertIds(expertIds);
+        Map<Long, Long> countMap = expertApplicationCountLookupPort.countByExpertIds(expertIds);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d46f64c and 9eeae3b.

📒 Files selected for processing (8)
  • src/main/java/starlight/adapter/expertApplication/persistence/ExpertApplicationJpaPort.java
  • src/main/java/starlight/adapter/expertReport/webapi/ExpertReportController.java
  • src/main/java/starlight/adapter/expertReport/webapi/dto/ExpertReportResponse.java
  • src/main/java/starlight/application/expert/ExpertDetailQueryService.java
  • src/main/java/starlight/application/expert/required/ExpertApplicationCountLookupPort.java
  • src/main/java/starlight/application/expertReport/ExpertReportService.java
  • src/main/java/starlight/application/expertReport/provided/dto/ExpertReportWithExpertResult.java
  • src/main/java/starlight/application/expertReport/required/ExpertApplicationCountLookupPort.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/starlight/application/expertReport/required/ExpertApplicationCountLookupPort.java (1)
src/main/java/starlight/application/expert/required/ExpertApplicationLookupPort.java (2)
  • ExpertApplicationLookupPort (6-9)
  • countByExpertIds (8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-and-push
  • GitHub Check: test
🔇 Additional comments (8)
src/main/java/starlight/application/expertReport/provided/dto/ExpertReportWithExpertResult.java (1)

6-16: LGTM! 깔끔한 Result DTO 구현입니다.

*Result 네이밍 컨벤션을 잘 따르고 있으며, 정적 팩토리 메서드 of()를 통해 명확한 생성 API를 제공합니다. applicationCount를 포함하여 전문가 지원 개수를 동적으로 전달할 수 있도록 잘 설계되었습니다.

src/main/java/starlight/application/expert/required/ExpertApplicationCountLookupPort.java (1)

6-8: 포트 네이밍 개선 - 의미가 명확해졌습니다.

ExpertApplicationLookupPortExpertApplicationCountLookupPort로 변경하여 카운트 조회 목적이 명확히 드러납니다. 다만, 앞서 언급한 대로 expertReport.required 패키지의 동일 인터페이스와 통합이 필요합니다.

src/main/java/starlight/adapter/expertApplication/persistence/ExpertApplicationJpaPort.java (1)

50-66: LGTM! countByExpertIds 구현이 잘 되어 있습니다.

  • null/empty 입력에 대한 방어적 처리
  • 일관된 예외 처리 패턴
  • 스트림을 활용한 깔끔한 Map 변환
src/main/java/starlight/adapter/expertReport/webapi/ExpertReportController.java (2)

30-42: LGTM! applicationCount가 올바르게 전파됩니다.

ExpertReportWithExpertResult에서 applicationCount()를 추출하여 fromEntities()로 전달하는 흐름이 잘 구현되었습니다. 이전 리뷰에서 지적된 하드코딩 0L 문제가 해결되었습니다.


48-54: LGTM! 단건 조회에서도 동일한 패턴 적용

getExpertReport 메서드에서도 applicationCount가 올바르게 전달되고 있습니다.

src/main/java/starlight/application/expertReport/ExpertReportService.java (3)

46-48: LGTM! ExpertApplicationCountLookupPort 의존성 주입

카운트 조회를 위한 새 포트가 올바르게 주입되었습니다. 필드명 일관성을 위해 expertApplicationCountLookupPort로 명명하는 것도 고려해볼 수 있습니다.


91-101: LGTM! 단건 조회에서 applicationCount 통합

getExpertReportWithExpert 메서드에서 전문가 ID 기반으로 지원 건수를 조회하고 ExpertReportWithExpertResult에 포함시키는 로직이 잘 구현되었습니다.


105-130: LGTM! 다건 조회에서도 효율적으로 applicationCount 처리

getExpertReportsWithExpertByBusinessPlanId에서 모든 전문가 ID를 한 번에 조회(countByExpertIds)하여 N+1 문제를 방지하고 있습니다. 배치 조회 패턴이 잘 적용되었습니다.

@SeongHo5356 SeongHo5356 requested a review from 2ghrms December 31, 2025 12:52
@SeongHo5356 SeongHo5356 changed the title Srlt 113 클래스명 정리 [SRLT-116] Refactor: 인증/인가 레이어 리팩터링 + 응답/문서 정리 Dec 31, 2025
@SeongHo5356 SeongHo5356 changed the title [SRLT-116] Refactor: 인증/인가 레이어 리팩터링 + 응답/문서 정리 [SRLT-116] Member, Auth 도메인 리팩터링하고 응답/문서 정리 Dec 31, 2025
@SeongHo5356 SeongHo5356 changed the title [SRLT-116] Member, Auth 도메인 리팩터링하고 응답/문서 정리 [SRLT-116] Member, Auth 도메인 패키지 구조를 정리한다 Dec 31, 2025
@SeongHo5356 SeongHo5356 added ✅ TEST test 관련 🐞 FIX Something isn't working 🧵 REFACTOR 코드 리팩토링 📝 DOCS 문서 관련 작업 labels Dec 31, 2025
Copy link
Member

@2ghrms 2ghrms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~

public ApiResponse<OrderPrepareResponse> prepareOrder(
@Valid @RequestBody OrderPrepareRequest request,
@AuthenticationPrincipal AuthDetails authDetails
@AuthenticationPrincipal AuthenticatedMember authDetails
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 변수명도 authenticatedMember로 통일해주면 좋을 것 같습니다~~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다음 pr에 같이 반영해서 올려놓을게!

@SeongHo5356 SeongHo5356 merged commit 565a21f into develop Jan 1, 2026
4 checks passed
@SeongHo5356 SeongHo5356 added the ☁️ DEPLOY 배포 관련 label Jan 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

☁️ DEPLOY 배포 관련 🐞 FIX Something isn't working 📝 DOCS 문서 관련 작업 🧵 REFACTOR 코드 리팩토링 ✅ TEST test 관련

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants