Skip to content

[WTH-236] 랜딩 문의하기 api 구현#45

Merged
soo0711 merged 8 commits intodevfrom
feat/WTH-236-랜딩-문의하기-API-구현
Mar 31, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-236-\ub79c\ub529-\ubb38\uc758\ud558\uae30-API-\uad6c\ud604"
Merged

[WTH-236] 랜딩 문의하기 api 구현#45
soo0711 merged 8 commits intodevfrom
feat/WTH-236-랜딩-문의하기-API-구현

Conversation

@soo0711
Copy link
Copy Markdown
Collaborator

@soo0711 soo0711 commented Mar 31, 2026

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

랜딩 문의하기 api를 구현했습니다.

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

노션 DB 저장
슬랙 알림 설정

Why

랜딩 페이지 문의를 받기 위해서

How

포트-어댑터로 구현
비동기 구현 -> 응답 속도가 중요하기 때문
필드: 이메일(255), 문의내용(1000)

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

스크린샷 2026-03-31 오후 2 09 52 스크린샷 2026-03-31 오후 2 10 14

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.
환경변수 서버에 넣어뒀습니당
DB: 문의내용, 이메일, 날짜로 저장

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

  • 새로운 기능

    • 사용자 문의 제출 기능 추가: 이메일과 메시지로 문의를 보낼 수 있습니다.
    • 문의 제출 시 백그라운드에서 자동 저장 및 알림 전송이 이루어집니다.
    • 문의 전송 성공에 대한 명확한 응답 코드가 추가되었습니다.
    • 문의 제출 API는 인증 없이 접근할 수 있도록 허용됩니다.
  • 개선 사항

    • 비동기 처리 활성화 및 전용 스레드풀로 처리 안정성 향상.
    • Slack/Notion 연동을 위한 설정 항목이 추가되어 외부 알림/저장이 구성 가능합니다.

@soo0711 soo0711 requested a review from hyxklee March 31, 2026 05:12
@soo0711 soo0711 self-assigned this Mar 31, 2026
@soo0711 soo0711 added the ✨ Feature 새로운 기능 추가 label Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccd37c33-3862-40ca-974c-46fd8c5111e3

📥 Commits

Reviewing files that changed from the base of the PR and between e484fec and 4c44f81.

📒 Files selected for processing (1)
  • src/main/kotlin/com/weeth/global/config/AsyncConfig.kt

📝 Walkthrough

Walkthrough

사용자 문의 기능을 추가했습니다: 요청 DTO와 유스케이스, 저장/알림 포트, Notion/Slack 어댑터, 컨트롤러 엔드포인트 및 관련 설정(비동기 처리·프로퍼티·애플리케이션 설정)을 도입했습니다.

Changes

Cohort / File(s) Summary
요청 DTO 및 응답 코드
src/main/kotlin/com/weeth/domain/user/application/dto/request/CreateInquiryRequest.kt, src/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.kt
CreateInquiryRequest DTO 추가(이메일·메시지 유효성·Swagger 주석) 및 INQUIRY_SEND_SUCCESS 응답 코드 추가
유스케이스 및 포트
src/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.kt, src/main/kotlin/com/weeth/domain/user/domain/port/InquirySavePort.kt, src/main/kotlin/com/weeth/domain/user/domain/port/InquiryNotifyPort.kt
문의 저장 및 알림 흐름을 담당하는 CreateInquiryUseCase와 저장/알림 포트 인터페이스 추가
외부 연동 어댑터
src/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.kt, src/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt
Notion에 페이지 생성으로 문의 저장(@Async) 및 Slack 웹훅으로 알림 전송(@Async)하는 어댑터 구현(HTTP 호출, 실패 시 경고 로그)
프레젠테이션 및 보안
src/main/kotlin/com/weeth/domain/user/presentation/UserController.kt, src/main/kotlin/com/weeth/global/config/SecurityConfig.kt
POST /api/v4/users/inquiries(컨트롤러 매핑은 /inquiries) 엔드포인트 추가 및 해당 경로 인증 제외 목록에 등록
설정 프로퍼티
src/main/kotlin/com/weeth/global/config/properties/NotionProperties.kt, src/main/kotlin/com/weeth/global/config/properties/SlackProperties.kt
notionslack 바인딩용 @ConfigurationProperties 클래스 추가(검증 어노테이션 포함)
애플리케이션 설정 및 비동기 구성
src/main/resources/application.yml, src/main/kotlin/com/weeth/WeethApplication.kt, src/main/kotlin/com/weeth/global/config/AsyncConfig.kt
환경변수 기반 Notion/Slack 설정 추가, @EnableAsync 활성화 및 ThreadPoolTaskExecutor 빈(taskExecutor) 등록

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • hyxklee
  • dalzzy

Poem

🐰 살금살금 문의가 왔네,
노션에 쓱쓱 기록하고,
슬랙에 조용히 속삭여,
비동기 발자국 남기며,
사용자의 목소리 모아볼게요! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 '랜딩 문의하기 API 구현'으로 변경사항의 핵심인 문의 API 구현을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명은 템플릿의 주요 섹션(Summary, Changes, Screenshots, Reviewer 참고사항, Checklist)을 모두 포함하고 있으며 충분한 정보를 제공합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-236-랜딩-문의하기-API-구현

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
Copy Markdown

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 784ac94 and 978b932.

📒 Files selected for processing (15)
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/CreateInquiryRequest.kt
  • src/main/kotlin/com/weeth/domain/user/application/exception/NotionApiException.kt
  • src/main/kotlin/com/weeth/domain/user/application/exception/SlackApiException.kt
  • src/main/kotlin/com/weeth/domain/user/application/exception/UserErrorCode.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/CreateInquiryUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/domain/port/InquiryNotifyPort.kt
  • src/main/kotlin/com/weeth/domain/user/domain/port/InquirySavePort.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/SlackInquiryNotifyAdapter.kt
  • src/main/kotlin/com/weeth/domain/user/presentation/UserController.kt
  • src/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.kt
  • src/main/kotlin/com/weeth/global/config/SecurityConfig.kt
  • src/main/kotlin/com/weeth/global/config/properties/NotionProperties.kt
  • src/main/kotlin/com/weeth/global/config/properties/SlackProperties.kt
  • src/main/resources/application.yml

Comment on lines +8 to +13
@Service
class CreateInquiryUseCase(
private val inquirySavePort: InquirySavePort,
private val inquiryNotifyPort: InquiryNotifyPort,
) {
fun execute(request: CreateInquiryRequest) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.

Comment on lines +14 to +15
inquirySavePort.save(request.email, request.message)
inquiryNotifyPort.notify(request.email, request.message)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

저장 성공 후 알림 실패 시 요청 재시도로 중복 저장 위험이 있습니다.

현재 순서에서는 저장이 이미 성공한 상태에서 알림 실패 예외가 나면 클라이언트는 실패로 인식해 재시도할 수 있고, 그 경우 동일 문의가 다시 저장될 수 있습니다. 저장과 알림의 성공 기준을 분리(예: 알림 비동기/재시도 큐)하는 쪽이 안전합니다.

🤖 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).

Copy link
Copy Markdown

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 978b932 and e484fec.

📒 Files selected for processing (3)
  • src/main/kotlin/com/weeth/WeethApplication.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/NotionInquirySaveAdapter.kt
  • src/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
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 31, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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가 사용됩니다. 이는 각 비동기 작업마다 새로운 스레드를 생성하므로, 문의 폭주 시 스레드 무한 생성으로 인한 메모리 고갈 및 시스템 마비를 초래할 수 있습니다.

다음 중 하나로 해결하세요:

  1. TaskExecutor Bean을 정의하고 스레드 풀 크기/큐 정책을 명시 (ThreadPoolTaskExecutor 권장)
  2. 또는 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AsyncConfig도 추가해두는게 조을 것 같아욤!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@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.

Copy link
Copy Markdown
Contributor

@hyxklee hyxklee left a comment

Choose a reason for hiding this comment

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

AsyncConfig만 추가하고 머지하면 될 것 같습니다! 고생하셨서용

import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity

@EnableAsync
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AsyncConfig도 추가해두는게 조을 것 같아욤!

@soo0711 soo0711 merged commit 646e267 into dev Mar 31, 2026
2 checks passed
@soo0711 soo0711 deleted the feat/WTH-236-랜딩-문의하기-API-구현 branch March 31, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants