Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough사용자 문의 기능을 추가했습니다: 요청 DTO와 유스케이스, 저장/알림 포트, Notion/Slack 어댑터, 컨트롤러 엔드포인트 및 관련 설정(비동기 처리·프로퍼티·애플리케이션 설정)을 도입했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UserController as "UserController\n(POST /inquiries)"
participant CreateInquiryUseCase as "CreateInquiryUseCase"
participant NotionAdapter as "NotionInquirySaveAdapter\n(InquirySavePort)"
participant SlackAdapter as "SlackInquiryNotifyAdapter\n(InquiryNotifyPort)"
participant NotionAPI as "Notion API"
participant SlackAPI as "Slack Webhook"
User->>UserController: POST /inquiries (CreateInquiryRequest)
UserController->>CreateInquiryUseCase: execute(request)
CreateInquiryUseCase->>NotionAdapter: save(email, message)
Note right of NotionAdapter: `@Async` 호출 — 비동기 처리
NotionAdapter->>NotionAPI: POST /v1/pages (db id, properties)
NotionAPI-->>NotionAdapter: 201 Created / error
CreateInquiryUseCase->>SlackAdapter: notify(email, message)
Note right of SlackAdapter: `@Async` 호출 — 비동기 처리
SlackAdapter->>SlackAPI: POST webhook ({"text": ...})
SlackAPI-->>SlackAdapter: 200 OK / error
CreateInquiryUseCase-->>UserController: return
UserController-->>User: CommonResponse.success(INQUIRY_SEND_SUCCESS)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/kotlin/com/weeth/global/config/SecurityConfig.kt (1)
48-49:permitAll범위를 POST로 한정하는 것이 더 안전합니다.Line 48은 경로 전체를 열어두고 있어, 이후 같은 URI에 다른 메서드가 추가되면 의도치 않게 무인증 접근이 허용될 수 있습니다.
POST만 명시해 두는 편이 안전합니다.제안 diff
.requestMatchers( "/api/v4/users/social/kakao", "/api/v4/users/social/apple", "/api/v4/users/social/refresh", - "/api/v4/users/inquiries", ).permitAll() + .requestMatchers(HttpMethod.POST, "/api/v4/users/inquiries") + .permitAll()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/global/config/SecurityConfig.kt` around lines 48 - 49, The current SecurityConfig uses permitAll() for the whole "/api/v4/users/inquiries" path which allows any HTTP method unauthenticated; change the authorization rule to only permit POST for that endpoint by using the HTTP method-specific matcher (e.g., HttpMethod.POST with the authorizeRequests/antMatchers or requestMatchers call) so only POST to "/api/v4/users/inquiries" is open while other methods remain protected; update the rule where permitAll() is applied in SecurityConfig to target POST only (reference the permitAll() usage and the "/api/v4/users/inquiries" path).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/kotlin/com/weeth/domain/user/application/exception/UserErrorCode.kt`:
- Around line 48-52: The NOTION_API_ERROR and SLACK_API_ERROR entries in
UserErrorCode use 30xx-style codes which conflict with the reserved server-error
range; update these enum constants (NOTION_API_ERROR and SLACK_API_ERROR) to use
the domain-specific error-code range allocated for the User domain (move them
out of the 3000-range), keep their HttpStatus and messages unchanged, and ensure
any references to these codes elsewhere in the codebase are updated to the new
numeric values so the enum and usages remain consistent.
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.kt`:
- Around line 14-15: 현재 CreateInquiryUseCase의 호출 순서(inquirySavePort.save(...);
inquiryNotifyPort.notify(...))는 저장 성공 후 알림 실패 시 클라이언트 재시도로 중복 저장이 발생할 수 있으므로,
inquirySavePort.save는 동기적으로 그대로 두고 inquiryNotifyPort.notify 호출을 비동기/내결함 방식으로
변경하세요; 구체적으로 CreateInquiryUseCase의 notify 호출을 즉시 백그라운드 작업(예: 비동기 실행기/메시지 큐로
전송)으로 옮기거나 notify 호출을 try-catch로 감싸 실패 시 예외를 전파하지 않고 retry 큐에 넣도록 구현해 저장과 알림의 성공
기준을 분리하고 중복 저장을 방지하세요 (참조: inquirySavePort.save, inquiryNotifyPort.notify,
CreateInquiryUseCase).
- Around line 8-13: The CreateInquiryUseCase.execute() method performs a write
via inquirySavePort.save() and must be executed within a transaction; annotate
it (or the CreateInquiryUseCase class) with `@Transactional` so the save operation
is wrapped in a transactional boundary, and ensure the proper Spring transaction
annotation is imported and applied to the CreateInquiryUseCase class or the
execute(request: CreateInquiryRequest) method to enforce rollback semantics on
failure.
---
Nitpick comments:
In `@src/main/kotlin/com/weeth/global/config/SecurityConfig.kt`:
- Around line 48-49: The current SecurityConfig uses permitAll() for the whole
"/api/v4/users/inquiries" path which allows any HTTP method unauthenticated;
change the authorization rule to only permit POST for that endpoint by using the
HTTP method-specific matcher (e.g., HttpMethod.POST with the
authorizeRequests/antMatchers or requestMatchers call) so only POST to
"/api/v4/users/inquiries" is open while other methods remain protected; update
the rule where permitAll() is applied in SecurityConfig to target POST only
(reference the permitAll() usage and the "/api/v4/users/inquiries" path).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f4da6bc8-1d30-4ab1-aae0-9453f9237b18
📒 Files selected for processing (15)
src/main/kotlin/com/weeth/domain/user/application/dto/request/CreateInquiryRequest.ktsrc/main/kotlin/com/weeth/domain/user/application/exception/NotionApiException.ktsrc/main/kotlin/com/weeth/domain/user/application/exception/SlackApiException.ktsrc/main/kotlin/com/weeth/domain/user/application/exception/UserErrorCode.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.ktsrc/main/kotlin/com/weeth/domain/user/domain/port/InquiryNotifyPort.ktsrc/main/kotlin/com/weeth/domain/user/domain/port/InquirySavePort.ktsrc/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.ktsrc/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserController.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.ktsrc/main/kotlin/com/weeth/global/config/SecurityConfig.ktsrc/main/kotlin/com/weeth/global/config/properties/NotionProperties.ktsrc/main/kotlin/com/weeth/global/config/properties/SlackProperties.ktsrc/main/resources/application.yml
src/main/kotlin/com/weeth/domain/user/application/exception/UserErrorCode.kt
Outdated
Show resolved
Hide resolved
| @Service | ||
| class CreateInquiryUseCase( | ||
| private val inquirySavePort: InquirySavePort, | ||
| private val inquiryNotifyPort: InquiryNotifyPort, | ||
| ) { | ||
| fun execute(request: CreateInquiryRequest) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# command usecase의 `@Transactional` 적용 현황 확인
fd -t f -e kt 'UseCase' src/main/kotlin/com/weeth/domain \
| xargs rg -n -C1 '@Transactional|class .*UseCase|fun execute\('Repository: Team-Weeth/weeth-server
Length of output: 26082
커맨드 UseCase에 @Transactional 애너테이션 추가 필요
CreateInquiryUseCase.execute() 메서드는 데이터 쓰기 작업(inquirySavePort.save())을 수행하는 커맨드 작업입니다. 가이드라인에 따라 커맨드 작업은 반드시 @Transactional 애너테이션을 적용해야 합니다.
수정 예시
import com.weeth.domain.user.domain.port.InquirySavePort
import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
`@Service`
class CreateInquiryUseCase(
private val inquirySavePort: InquirySavePort,
private val inquiryNotifyPort: InquiryNotifyPort,
) {
+ `@Transactional`
fun execute(request: CreateInquiryRequest) {
inquirySavePort.save(request.email, request.message)
inquiryNotifyPort.notify(request.email, request.message)
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.kt`
around lines 8 - 13, The CreateInquiryUseCase.execute() method performs a write
via inquirySavePort.save() and must be executed within a transaction; annotate
it (or the CreateInquiryUseCase class) with `@Transactional` so the save operation
is wrapped in a transactional boundary, and ensure the proper Spring transaction
annotation is imported and applied to the CreateInquiryUseCase class or the
execute(request: CreateInquiryRequest) method to enforce rollback semantics on
failure.
| inquirySavePort.save(request.email, request.message) | ||
| inquiryNotifyPort.notify(request.email, request.message) |
There was a problem hiding this comment.
저장 성공 후 알림 실패 시 요청 재시도로 중복 저장 위험이 있습니다.
현재 순서에서는 저장이 이미 성공한 상태에서 알림 실패 예외가 나면 클라이언트는 실패로 인식해 재시도할 수 있고, 그 경우 동일 문의가 다시 저장될 수 있습니다. 저장과 알림의 성공 기준을 분리(예: 알림 비동기/재시도 큐)하는 쪽이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.kt`
around lines 14 - 15, 현재 CreateInquiryUseCase의 호출 순서(inquirySavePort.save(...);
inquiryNotifyPort.notify(...))는 저장 성공 후 알림 실패 시 클라이언트 재시도로 중복 저장이 발생할 수 있으므로,
inquirySavePort.save는 동기적으로 그대로 두고 inquiryNotifyPort.notify 호출을 비동기/내결함 방식으로
변경하세요; 구체적으로 CreateInquiryUseCase의 notify 호출을 즉시 백그라운드 작업(예: 비동기 실행기/메시지 큐로
전송)으로 옮기거나 notify 호출을 try-catch로 감싸 실패 시 예외를 전파하지 않고 retry 큐에 넣도록 구현해 저장과 알림의 성공
기준을 분리하고 중복 저장을 방지하세요 (참조: inquirySavePort.save, inquiryNotifyPort.notify,
CreateInquiryUseCase).
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt (1)
33-33: 실패 로그에 스택트레이스를 포함해 장애 분석 가능성을 높여주세요.Line [33]은 현재
e.message만 남겨서 원인 추적이 어려울 수 있습니다. 예외 객체 자체를 함께 로깅하는 편이 운영 대응에 유리합니다.제안 diff
- }.onFailure { e -> log.warn("Slack 알림 전송 실패: {}", e.message) } + }.onFailure { e -> + log.warn("Slack 알림 전송 실패", e) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt` at line 33, The failure log in SlackInquiryNotifyAdapter currently only logs e.message which hides the stacktrace; change the onFailure handler (the lambda passed to onFailure where you call log.warn("Slack 알림 전송 실패: {}", e.message)) to log the exception itself so the stacktrace is captured (e.g., pass the Throwable e as the exception parameter to the logger while keeping a clear message), ensuring full error context is recorded for troubleshooting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/com/weeth/WeethApplication.kt`:
- Line 11: You enabled `@EnableAsync` but didn't provide a TaskExecutor, so Spring
will use SimpleAsyncTaskExecutor (unsafe under load); add a
ThreadPoolTaskExecutor bean (e.g., in the WeethApplication class or a new
`@Configuration`) and configure corePoolSize, maxPoolSize, queueCapacity and
threadNamePrefix, then return/initialize it as the async executor (or mark it
`@Primary` or name it "taskExecutor") so async methods like
NotionInquirySaveAdapter.save() and SlackInquiryNotifyAdapter.notify() run on a
bounded thread pool; alternatively set spring.task.execution.pool.* properties
in application.properties/yaml to provide the same pool sizing.
---
Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt`:
- Line 33: The failure log in SlackInquiryNotifyAdapter currently only logs
e.message which hides the stacktrace; change the onFailure handler (the lambda
passed to onFailure where you call log.warn("Slack 알림 전송 실패: {}", e.message)) to
log the exception itself so the stacktrace is captured (e.g., pass the Throwable
e as the exception parameter to the logger while keeping a clear message),
ensuring full error context is recorded for troubleshooting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cfb3e703-b921-4617-8cba-7e37e2dd953b
📒 Files selected for processing (3)
src/main/kotlin/com/weeth/WeethApplication.ktsrc/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.ktsrc/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.kt
| import org.springframework.scheduling.annotation.EnableScheduling | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity | ||
|
|
||
| @EnableAsync |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Async 관련 설정/코드 탐색"
rg -n -C3 --type=kt '@EnableAsync|@Async|AsyncConfigurer|TaskExecutor|ThreadPoolTaskExecutor'
echo
echo "2) TaskExecutor Bean 정의 확인"
rg -n -C3 --type=kt '@Bean|TaskExecutor|Executor'
echo
echo "3) application 설정에서 task execution 관련 프로퍼티 확인"
fd -i 'application*.yml' src/main/resources -x sh -c 'echo "== {} =="; rg -n -C2 "spring\\.task\\.execution|task\\.execution|async" "{}" || true'
fd -i 'application*.yaml' src/main/resources -x sh -c 'echo "== {} =="; rg -n -C2 "spring\\.task\\.execution|task\\.execution|async" "{}" || true'
fd -i 'application*.properties' src/main/resources -x sh -c 'echo "== {} =="; rg -n -C2 "spring\\.task\\.execution|task\\.execution|async" "{}" || true'Repository: Team-Weeth/weeth-server
Length of output: 131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Async 관련 Kotlin 코드 탐색 (`@EnableAsync`, `@Async`, AsyncConfigurer, TaskExecutor 등)"
rg -n -C3 '@EnableAsync|@Async|AsyncConfigurer|TaskExecutor|ThreadPoolTaskExecutor' -g '*.kt' || echo "(No async-related annotations found)"
echo
echo "2) TaskExecutor Bean 정의 확인"
rg -n -C3 '@Bean|TaskExecutor|Executor' -g '*.kt' | head -100 || echo "(No bean definitions found)"
echo
echo "3) WeethApplication.kt 파일 확인"
cat -n src/main/kotlin/com/weeth/WeethApplication.kt
echo
echo "4) application 설정 파일 탐색"
find src/main/resources -type f \( -name 'application*.yml' -o -name 'application*.yaml' -o -name 'application*.properties' \) -exec sh -c 'echo "== {} =="; cat "{}"' \;
echo
echo "5) Spring task execution 프로퍼티 확인"
rg -n 'spring\.task\.execution|task\.execution|async' || echo "(No task execution properties found)"Repository: Team-Weeth/weeth-server
Length of output: 16729
@EnableAsync 활성화 시 TaskExecutor 설정이 누락되었습니다. 즉시 수정이 필요합니다.
현재 코드베이스에서 @Async 메서드가 실제로 사용되고 있습니다:
NotionInquirySaveAdapter.save()(외부 Notion API 호출)SlackInquiryNotifyAdapter.notify()(외부 Slack API 호출)
하지만 TaskExecutor 설정이 없어 Spring의 기본값인 SimpleAsyncTaskExecutor가 사용됩니다. 이는 각 비동기 작업마다 새로운 스레드를 생성하므로, 문의 폭주 시 스레드 무한 생성으로 인한 메모리 고갈 및 시스템 마비를 초래할 수 있습니다.
다음 중 하나로 해결하세요:
TaskExecutorBean을 정의하고 스레드 풀 크기/큐 정책을 명시 (ThreadPoolTaskExecutor권장)- 또는
spring.task.execution.*프로퍼티로 스레드 풀 설정 (예:spring.task.execution.pool.core-size,max-size,queue-capacity)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/kotlin/com/weeth/WeethApplication.kt` at line 11, You enabled
`@EnableAsync` but didn't provide a TaskExecutor, so Spring will use
SimpleAsyncTaskExecutor (unsafe under load); add a ThreadPoolTaskExecutor bean
(e.g., in the WeethApplication class or a new `@Configuration`) and configure
corePoolSize, maxPoolSize, queueCapacity and threadNamePrefix, then
return/initialize it as the async executor (or mark it `@Primary` or name it
"taskExecutor") so async methods like NotionInquirySaveAdapter.save() and
SlackInquiryNotifyAdapter.notify() run on a bounded thread pool; alternatively
set spring.task.execution.pool.* properties in application.properties/yaml to
provide the same pool sizing.
There was a problem hiding this comment.
AsyncConfig도 추가해두는게 조을 것 같아욤!
There was a problem hiding this comment.
@hyxklee 동의해주셔서 감사합니다! 아래와 같이 AsyncConfig를 추가하시면 됩니다.
src/main/kotlin/com/weeth/global/config/AsyncConfig.kt 파일을 새로 생성해 주세요:
package com.weeth.global.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import java.util.concurrent.Executor
`@Configuration`
class AsyncConfig {
`@Bean`(name = ["taskExecutor"])
fun taskExecutor(): Executor {
return ThreadPoolTaskExecutor().apply {
corePoolSize = 5
maxPoolSize = 20
queueCapacity = 100
setThreadNamePrefix("async-inquiry-")
initialize()
}
}
}또는 별도 클래스 없이 application.yml에 다음을 추가하는 방법도 있습니다:
spring:
task:
execution:
pool:
core-size: 5
max-size: 20
queue-capacity: 100
thread-name-prefix: async-inquiry-설정값 근거 (Notion/Slack 같은 외부 API 호출 위주의 I/O-bound 작업 기준):
| 파라미터 | 권장값 | 이유 |
|---|---|---|
corePoolSize |
5 | 기본 유지 스레드 수 |
maxPoolSize |
20 | 트래픽 급증 시 최대 확장 한계 |
queueCapacity |
100 | 최대 스레드 초과 시 대기 큐 |
threadNamePrefix |
async-inquiry- |
스레드 덤프/로그에서 식별 용이 |
queueCapacity가 가득 차고 maxPoolSize에 도달하면 TaskRejectedException이 발생하므로, 필요 시 RejectedExecutionHandler도 함께 설정하시면 더욱 안전합니다.
🧠 Learnings used
Learnt from: hyxklee
Repo: Team-Weeth/weeth-server PR: 44
File: src/main/kotlin/com/weeth/domain/user/presentation/SocialCallbackController.kt:55-60
Timestamp: 2026-03-31T06:54:23.087Z
Learning: When reviewing Spring code in Kotlin, note that `UriComponentsBuilder.toUriString()` performs URL encoding internally (it effectively calls `encode().build().toUriString()`), including non-ASCII characters such as Korean. Therefore, do not flag `UriComponentsBuilder.toUriString()` as “missing encoding”. Only `UriComponents.toUriString()` is simple string concatenation and does not apply encoding by default.
hyxklee
left a comment
There was a problem hiding this comment.
AsyncConfig만 추가하고 머지하면 될 것 같습니다! 고생하셨서용
| import org.springframework.scheduling.annotation.EnableScheduling | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity | ||
|
|
||
| @EnableAsync |
There was a problem hiding this comment.
AsyncConfig도 추가해두는게 조을 것 같아욤!
📌 Summary
랜딩 문의하기 api를 구현했습니다.
📝 Changes
What
노션 DB 저장
슬랙 알림 설정
Why
랜딩 페이지 문의를 받기 위해서
How
포트-어댑터로 구현
비동기 구현 -> 응답 속도가 중요하기 때문
필드: 이메일(255), 문의내용(1000)
📸 Screenshots / Logs
💡 Reviewer 참고사항
✅ Checklist
Summary by CodeRabbit
새로운 기능
개선 사항