Skip to content

feat: 센트리 웹훅 수신 기능 구현 완료#2

Merged
move-hoon merged 29 commits intomainfrom
feat/#1
Apr 19, 2025
Merged

feat: 센트리 웹훅 수신 기능 구현 완료#2
move-hoon merged 29 commits intomainfrom
feat/#1

Conversation

@move-hoon
Copy link
Member

@move-hoon move-hoon commented Apr 18, 2025

Related issue 🛠

Work Description ✏️

패키지 구조

org.sopt.makers
├── dto
│   ├── SentryEventDetail.java
│   └── WebhookRequest.java
├── global
│   ├── config
│   │   └── ObjectMapperConfig.java
│   ├── constant
│   │   ├── Color.java
│   │   └── SlackConstant.java
│   ├── exception
│   │   ├── base
│   │   │   └── BaseErrorCode.java
│   │   ├── checked
│   │   │   ├── SentryCheckedException.java
│   │   │   └── SlackMessageBuildException.java
│   │   │   └── SlackSendException.java
│   │   ├── unchecked
│   │   │   ├── SentryUncheckedException.java
│   │   │   └── InvalidSlackPayloadException.java
│   │   └── message
│   │       └── ErrorMessage.java
│   └── util
│       ├── EnvUtil.java
│       └── HttpClientUtil.java
├── handler
│   └── SentryWebhookHandler.java
├── service
│   ├── NotificationService.java (인터페이스)
│   ├── NotificationServiceFactory.java
│   └── SlackNotificationService.java
└── vo
    ├── slack
        ├── block
        │   ├── Block.java (인터페이스)
        │   ├── HeaderBlock.java
        │   ├── SectionBlock.java
        │   └── ActionsBlock.java
        ├── element
        │   ├── Element.java (인터페이스)
        │   └── Button.java
        ├── text
        │   └── Text.java
        └── SlackMessage.java

주요 내용

  • Sentry에서 발생한 이벤트를 감지하고 Slack으로 알림을 전송하는 AWS Lambda 핸들러를 구현했습니다.
  • Value Object(VO) 패턴을 적용하여 Slack 메시지 구성 요소(Block, Element, Text 등)를 타입 안전하게 구현했습니다.
  • 예외 처리 체계를 구축하여 checked/unchecked 예외를 명확히 구분했습니다.
  • 환경 변수를 기반으로 Webhook URL을 자동으로 조회하는 유틸리티를 구현했습니다.
  • 서비스 타입(slack, discord 등)에 따라 적절한 알림 서비스를 생성하는 팩토리 패턴을 적용했습니다.
  • JSON 처리를 위한 ObjectMapper 설정을 싱글톤 패턴으로 구현하여 효율성을 높였습니다.

구성 요소

1. Webhook 핸들러 (SentryWebhookHandler):

  • API Gateway에서 전달받은 Sentry 웹훅 이벤트를 처리
  • 경로 파라미터 및 이벤트 데이터 추출
  • 예외 처리 및 적절한 HTTP 응답 반환

2. 알림 서비스 계층:

  • NotificationService 인터페이스와 SlackNotificationService 구현체
  • 서비스 타입에 따라 적절한 구현체를 제공하는 NotificationServiceFactory

3. Slack 메시지 VO 객체:

  • Block, Element, Text 등 Slack 메시지 구성 요소를 타입 안전한 객체로 구현
  • 불변성(immutability)을 보장하는 Record 클래스 사용
  • 각 클래스에 newInstance 패턴의 정적 팩토리 메서드 구현

4. 예외 처리 체계:

  • BaseErrorCode 인터페이스를 기반으로 한 일관된 에러 코드 및 메시지 관리
  • SentryCheckedException(체크드 예외)과 SentryUncheckedException(언체크드 예외) 구분
  • 각 상황에 맞는 구체적인 예외 클래스 정의

5. 유틸리티 클래스:

  • HTTP 요청 처리를 위한 HttpClientUtil
  • 환경변수 기반 Webhook URL 관리를 위한 EnvUtil
  • ObjectMapper 설정을 위한 ObjectMapperConfig

기술적 결정 사항

  • JSON 처리에 Jackson 라이브러리 활용
  • Record 타입을 사용하여 VO 구현
  • 팩토리 패턴, 싱글톤 패턴 등 적절한 디자인 패턴 적용
  • Lambda 핸들러를 위한 AWS SDK 활용

Trouble Shooting ⚽️

  • None

- BaseErrorCode 인터페이스: status, code, message를 포함한 표준 에러 형식 정의
- SentryException 인터페이스: Sentry 연동을 위한 예외 포맷 정의
- Sentry 연동을 위한 SentryUncheckedException 생성
- InvalidSlackPayloadException 정의 및 BaseErrorCode 기반 예외 생성 메서드 추가
- 메시지 생성 실패: SlackMessageBuildException
- 전송 실패: SlackSendException
- Sentry 연동을 위한 SentryCheckedException 기반 설계
- 서비스 유형별 환경 변수 prefix 매핑
- Slack, Discord 지원
- 웹훅 URL 누락 및 미지원 서비스 유형에 대한 커스텀 예외 처리 추가
- 서비스 유형별 환경 변수 prefix 매핑
- Slack, Discord 지원
- 웹훅 URL 누락 및 미지원 서비스 유형에 대한 커스텀 예외 처리 추가
- Java 11 HttpClient 기반 POST 요청 전송 유틸리티 추가
- 기본 timeout 10초 설정
- Content-Type 지정 가능
- SlackConstant 클래스 생성: Slack 메시지 포맷에 필요한 상수 정의 (날짜 포맷, 블록/텍스트/요소 타입, JSON 키 등)
- Color enum 클래스 추가: 로그 레벨(fatal, error, info 등)에 따라 색상 코드를 매핑하는 getColorByLevel 메서드 구현
- NotificationService 인터페이스 정의: 알림 전송 기능을 위한 메서드 정의
- SlackNotificationService 클래스 구현: Slack 알림 메시지를 생성하고 전송하는 로직 구현
- NotificationServiceFactory 클래스 구현: 서비스 유형에 맞는 알림 서비스 구현체를 반환하는 팩토리 메서드 추가
- 지원하지 않는 서비스 유형에 대한 예외 처리: 서비스 유형이 지원되지 않으면 예외를 발생시키도록 처리
- ObjectMapper 인스턴스를 싱글턴 방식으로 생성하여 재사용할 수 있도록 설정
- Jackson 설정:
  - NULL 값을 포함하지 않도록 설정
  - 알 수 없는 프로퍼티를 무시하도록 설정
  - 날짜를 타임스탬프 대신 ISO 8601 형식으로 출력하도록 설정
- ObjectMapper 인스턴스를 안전하게 반환하는 메서드 제공
- Sentry 이벤트 정보를 담기 위한 DTO 클래스 추가
- `issueId`, `webUrl`, `message`, `datetime`, `level` 필드 포함
- `JsonNode`를 이용해 Sentry 이벤트 데이터를 DTO로 변환하는 `from` 메서드 제공
@move-hoon move-hoon added the feat label Apr 18, 2025
@move-hoon move-hoon self-assigned this Apr 18, 2025
@move-hoon move-hoon linked an issue Apr 18, 2025 that may be closed by this pull request
1 task
@height
Copy link

height bot commented Apr 18, 2025

Link Height tasks by mentioning a task ID in the pull request title or commit messages, or description and comments with the keyword link (e.g. "Link T-123").

💡Tip: You can also use "Close T-X" to automatically close a task when the pull request is merged.

@coderabbitai
Copy link

coderabbitai bot commented Apr 18, 2025

Summary by CodeRabbit

  • 신규 기능

    • Sentry 이벤트 웹훅을 수신하고 Slack 알림을 전송하는 AWS Lambda 핸들러 도입
    • Slack 알림 메시지의 색상, 구조, 버튼 등 다양한 블록 및 요소 지원
    • 환경 변수 기반의 Webhook URL 자동 조회 및 서비스 타입별 NotificationService 팩토리 제공
    • 에러 코드 및 예외 처리 체계화(체크/언체크 예외, 에러 메시지 일원화)
    • Slack 메시지 포맷을 위한 다양한 블록, 텍스트, 버튼 등 VO 클래스 추가
  • 문서

    • AWS Lambda를 순수 Java로 구현한 배경과 기술적 이유 추가
  • CI/CD 및 설정

    • Release Drafter 자동 릴리스 노트 및 워크플로우 설정 추가
  • 기타

    • 기존 "Hello world!" 메인 클래스 제거
      """

Summary by CodeRabbit

  • 신규 기능

    • Sentry 웹훅 이벤트를 처리하는 AWS Lambda 핸들러가 추가되었습니다.
    • Sentry 이벤트를 Slack으로 알림 전송하는 기능이 도입되었습니다.
    • 다양한 예외 상황(웹훅 URL 누락, 지원하지 않는 서비스 타입, Slack 메시지 빌드/전송 오류 등)에 대한 세분화된 예외 및 에러 메시지가 제공됩니다.
    • Slack 메시지 블록(헤더, 섹션, 액션) 및 버튼, 텍스트 등 메시지 구조화 지원이 추가되었습니다.
    • 환경 변수 기반으로 서비스별 웹훅 URL을 동적으로 조회할 수 있습니다.
  • 문서

    • AWS Lambda를 순수 Java로 구현한 배경과 기술적 선택 이유가 README에 추가되었습니다.
  • 기타

    • 기존 "Hello world!" 출력 메인 클래스가 삭제되었습니다.

Summary by CodeRabbit

  • New Features

    • Sentry 이벤트를 처리하는 AWS Lambda 기반 웹훅 핸들러가 추가되었습니다.
    • Sentry 이벤트 상세 정보를 Slack 메시지로 전송하는 기능이 도입되었습니다.
    • Slack 메시지 형식 및 색상, 블록 구조 등 다양한 메시지 커스터마이즈가 가능합니다.
    • 환경 변수 기반 Slack/Discord Webhook URL 관리 기능이 추가되었습니다.
    • 서비스 타입별 Notification 서비스 팩토리 및 예외 처리 체계가 도입되었습니다.
  • Bug Fixes

    • "Hello world!"만 출력하던 예제 메인 클래스가 제거되었습니다.

Walkthrough

이번 변경에서는 Sentry Webhook을 수신하고, Slack 등으로 알림을 전송하는 전체 플로우가 구현되었습니다. AWS Lambda 환경에서 동작하는 핸들러가 추가되었으며, Sentry 이벤트 파싱, 환경 변수 기반 Webhook URL 조회, 서비스 타입별 NotificationService 팩토리 및 Slack 메시지 생성/전송 로직이 포함되었습니다. 또한, 예외 처리 체계와 Slack 메시지 데이터 구조, 색상 및 상수 관리, 공통 유틸리티 등이 새롭게 도입되었습니다. 기존의 Main 진입점은 삭제되었습니다.

Changes

파일/경로 요약 변경 요약
src/main/java/org/sopt/makers/Main.java 기존 Main 클래스 및 main 메서드 삭제
.../dto/SentryEventDetail.java, .../dto/WebhookRequest.java Sentry 이벤트/웹훅 요청용 DTO 레코드 및 정적 팩토리 메서드 추가
.../global/config/ObjectMapperConfig.java Jackson ObjectMapper 싱글턴 설정 클래스 추가
.../global/constant/Color.java, .../global/constant/SlackConstant.java 색상 및 Slack 메시지 관련 상수/enum 정의
.../global/exception/base/*, .../global/exception/checked/*, .../global/exception/unchecked/*,
.../global/exception/message/ErrorMessage.java
예외 처리 체계 도입: BaseErrorCode, SentryException, Checked/Unchecked Exception, 에러 메시지 enum 등 추가
.../global/util/EnvUtil.java, .../global/util/HttpClientUtil.java Webhook URL 환경변수 조회 및 HTTP POST 유틸리티 클래스 추가
.../handler/SentryWebhookHandler.java Sentry Webhook 수신 및 Slack 알림 전송 AWS Lambda 핸들러 구현
.../service/NotificationService.java, .../service/NotificationServiceFactory.java,
.../service/SlackNotificationService.java
NotificationService 인터페이스, 팩토리, Slack 구현체 추가
.../vo/slack/block/*, .../vo/slack/element/*, .../vo/slack/message/SlackMessage.java,
.../vo/slack/text/Text.java
Slack 메시지 Block/Element/Text 구조체 및 팩토리 메서드 일괄 추가

Sequence Diagram(s)

sequenceDiagram
    participant API Gateway
    participant Lambda(SentryWebhookHandler)
    participant NotificationServiceFactory
    participant SlackNotificationService
    participant EnvUtil
    participant HttpClientUtil

    API Gateway->>Lambda(SentryWebhookHandler): Webhook 요청 전달
    Lambda(SentryWebhookHandler)->>Lambda(SentryWebhookHandler): WebhookRequest/이벤트 파싱
    Lambda(SentryWebhookHandler)->>EnvUtil: Webhook URL 조회
    Lambda(SentryWebhookHandler)->>NotificationServiceFactory: 서비스 타입별 NotificationService 생성
    Lambda(SentryWebhookHandler)->>SlackNotificationService: sendNotification 호출
    SlackNotificationService->>HttpClientUtil: Slack Webhook POST 요청
    HttpClientUtil-->>SlackNotificationService: 응답 반환
    SlackNotificationService-->>Lambda(SentryWebhookHandler): 처리 결과 반환
    Lambda(SentryWebhookHandler)-->>API Gateway: HTTP 응답 반환
Loading

Assessment against linked issues

Objective (issue) Addressed Explanation
기본 Sentry Webhook 수신 기능 구현 (#1)

Poem

(\(\
( -.-) 🥕
o_(")(")

Sentry 소식 토끼가 듣고
슬랙에 척척 알림을 띄워요
환경변수로 URL도 찾고
블록 메시지 색상도 곱게 칠하고
예외 처리 꼼꼼하게 챙긴 오늘,
토끼는 기쁜 마음으로
"성공!"을 외쳐봅니다!
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75f6d24 and 474bf2d.

📒 Files selected for processing (2)
  • .github/release-drafter-config.yml (1 hunks)
  • .github/workflows/release-drafter.yml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • .github/release-drafter-config.yml
  • .github/workflows/release-drafter.yml

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Nitpick comments (6)
src/main/java/org/sopt/makers/global/constant/Color.java (1)

22-33: getColorByLevel 메서드 구현이 견고합니다.

메서드가 null 체크와 입력 정규화를 적절히 처리하고 있어 안정적입니다. 또한 기본값으로 GRAY를 사용하는 것은 좋은 접근법입니다. 다만, 성능 최적화를 위해 다음과 같은 개선을 고려해 보세요:

현재 구현은 매번 모든 색상을 스트림으로 처리하고 있습니다. 정적 Map을 사용하면 성능을 향상시킬 수 있습니다:

@Getter
@RequiredArgsConstructor
public enum Color {
    RED("#FF0000", Set.of("fatal", "critical")),
    ORANGE("#E36209", Set.of("error")),
    YELLOW("#FFCC00", Set.of("warning")),
    GREEN("#36A64F", Set.of("info")),
    BLUE("#87CEFA", Set.of("debug")),
    GRAY("#AAAAAA", Set.of());

    private final String value;
    private final Set<String> levels;
    
+   private static final Map<String, String> LEVEL_TO_COLOR_MAP = new HashMap<>();
+   
+   static {
+       for (Color color : Color.values()) {
+           for (String level : color.getLevels()) {
+               LEVEL_TO_COLOR_MAP.put(level, color.getValue());
+           }
+       }
+   }

    public static String getColorByLevel(String level) {
        if (level == null || level.trim().isEmpty()) {
            return GRAY.value;
        }

        String normalized = level.trim().toLowerCase();
-       return Arrays.stream(Color.values())
-           .filter(color -> color.levels.contains(normalized))
-           .map(Color::getValue)
-           .findFirst()
-           .orElse(GRAY.value);
+       return LEVEL_TO_COLOR_MAP.getOrDefault(normalized, GRAY.value);
    }
}
src/main/java/org/sopt/makers/dto/SentryEventDetail.java (1)

16-24: JSON 변환 시 null/empty 처리를 고려해보세요.

path() 메소드를 사용하여 안전하게 JSON 속성에 접근하고 있지만, 속성이 없거나 null인 경우에 대한 추가 처리가 없습니다. 이 경우 문자열 "null"이 반환될 수 있습니다.

다음과 같이 개선할 수 있습니다:

public static SentryEventDetail from(JsonNode eventNode) {
    return SentryEventDetail.builder()
-        .issueId(eventNode.path("issue_id").asText())
-        .webUrl(eventNode.path("web_url").asText())
-        .message(eventNode.path("message").asText())
-        .datetime(eventNode.path("datetime").asText())
-        .level(eventNode.path("level").asText())
+        .issueId(eventNode.path("issue_id").asText(""))
+        .webUrl(eventNode.path("web_url").asText(""))
+        .message(eventNode.path("message").asText(""))
+        .datetime(eventNode.path("datetime").asText(""))
+        .level(eventNode.path("level").asText(""))
        .build();
}

또는 null 체크를 추가하는 방식도 고려할 수 있습니다:

public static SentryEventDetail from(JsonNode eventNode) {
+    String issueId = eventNode.path("issue_id").isNull() ? "" : eventNode.path("issue_id").asText();
+    String webUrl = eventNode.path("web_url").isNull() ? "" : eventNode.path("web_url").asText();
+    String message = eventNode.path("message").isNull() ? "" : eventNode.path("message").asText();
+    String datetime = eventNode.path("datetime").isNull() ? "" : eventNode.path("datetime").asText();
+    String level = eventNode.path("level").isNull() ? "" : eventNode.path("level").asText();

    return SentryEventDetail.builder()
-        .issueId(eventNode.path("issue_id").asText())
-        .webUrl(eventNode.path("web_url").asText())
-        .message(eventNode.path("message").asText())
-        .datetime(eventNode.path("datetime").asText())
-        .level(eventNode.path("level").asText())
+        .issueId(issueId)
+        .webUrl(webUrl)
+        .message(message)
+        .datetime(datetime)
+        .level(level)
        .build();
}
src/main/java/org/sopt/makers/global/exception/unchecked/SentryUncheckedException.java (1)

8-16: 예외 계층 구조가 잘 설계되었습니다.

SentryException 인터페이스를 구현하고 BaseErrorCode를 포함하는 방식으로 예외 클래스가 잘 설계되었습니다. 다만, RuntimeException은 Serializable을 구현하므로 serialVersionUID를 추가하는 것을 고려해 보세요.

@Getter
public class SentryUncheckedException extends RuntimeException implements SentryException {
+	private static final long serialVersionUID = 1L;
	private final BaseErrorCode baseErrorCode;

	protected SentryUncheckedException(BaseErrorCode baseErrorCode) {
		super(baseErrorCode.getMessage());
		this.baseErrorCode = baseErrorCode;
	}
}
src/main/java/org/sopt/makers/global/util/EnvUtil.java (1)

36-44: 환경 변수 값 캐싱 고려

System.getenv() 호출은 비용이 크지 않지만, 자주 호출되는 경우 환경 변수 값을 캐싱하는 것을 고려해 볼 수 있습니다. 특히 애플리케이션 실행 중에 환경 변수가 변경되지 않는다면 더욱 효율적일 수 있습니다.

src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1)

14-17: 사용되지 않는 상수 제거 고려

HEADER_CONTENT_TYPE 는 현재 코드에서 참조되지 않습니다.
불필요한 상수는 제거하여 유지보수 오버헤드를 줄여 주세요.

src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1)

11-17: HTTP 상태 코드 재검토 권장

INVALID_SLACK_PAYLOAD(잘못된 요청)는 400, SLACK_NETWORK_ERROR(외부 서비스 연결 실패)는 504(게이트웨이 타임아웃) 등이 REST 관례에 더 부합합니다.
상태 코드를 조정해 API 일관성을 높여 보세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23d2dbd and 44d709d.

📒 Files selected for processing (20)
  • src/main/java/org/sopt/makers/Main.java (0 hunks)
  • src/main/java/org/sopt/makers/dto/SentryEventDetail.java (1 hunks)
  • src/main/java/org/sopt/makers/global/config/ObjectMapperConfig.java (1 hunks)
  • src/main/java/org/sopt/makers/global/constant/Color.java (1 hunks)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/base/BaseErrorCode.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/base/SentryException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/checked/SentryCheckedException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/checked/SlackMessageBuildException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/checked/SlackSendException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/unchecked/InvalidSlackPayloadException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/unchecked/SentryUncheckedException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/unchecked/UnsupportedServiceTypeException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/unchecked/WebhookUrlNotFoundException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/util/EnvUtil.java (1 hunks)
  • src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1 hunks)
  • src/main/java/org/sopt/makers/service/NotificationService.java (1 hunks)
  • src/main/java/org/sopt/makers/service/NotificationServiceFactory.java (1 hunks)
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/org/sopt/makers/Main.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/org/sopt/makers/service/NotificationService.java (2)
src/main/java/org/sopt/makers/global/exception/checked/SlackMessageBuildException.java (1)
  • SlackMessageBuildException (5-13)
src/main/java/org/sopt/makers/global/exception/checked/SlackSendException.java (1)
  • SlackSendException (5-13)
src/main/java/org/sopt/makers/global/util/EnvUtil.java (2)
src/main/java/org/sopt/makers/global/exception/unchecked/UnsupportedServiceTypeException.java (1)
  • UnsupportedServiceTypeException (5-13)
src/main/java/org/sopt/makers/global/exception/unchecked/WebhookUrlNotFoundException.java (1)
  • WebhookUrlNotFoundException (5-13)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (3)
src/main/java/org/sopt/makers/global/exception/checked/SlackMessageBuildException.java (1)
  • SlackMessageBuildException (5-13)
src/main/java/org/sopt/makers/global/exception/checked/SlackSendException.java (1)
  • SlackSendException (5-13)
src/main/java/org/sopt/makers/service/NotificationServiceFactory.java (1)
  • Slf4j (13-38)
src/main/java/org/sopt/makers/service/NotificationServiceFactory.java (2)
src/main/java/org/sopt/makers/global/exception/unchecked/UnsupportedServiceTypeException.java (1)
  • UnsupportedServiceTypeException (5-13)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (1)
  • Slf4j (29-193)
🔇 Additional comments (18)
src/main/java/org/sopt/makers/global/exception/base/BaseErrorCode.java (1)

3-7: 인터페이스 설계가 명확하고 간결합니다.

오류 코드를 위한 인터페이스 설계가 잘 되어 있습니다. HTTP 상태 코드, 오류 코드, 오류 메시지를 일관되게 제공하는 메서드를 명확하게 정의했습니다. 이 인터페이스는 예외 처리 시스템에 표준화된 오류 정보를 제공하기 위한 좋은 기반이 될 것입니다.

src/main/java/org/sopt/makers/global/exception/base/SentryException.java (1)

3-5: Sentry 통합을 위한 인터페이스 설계가 잘 되어 있습니다.

Sentry 관련 예외를 식별하고 적절한 오류 코드를 제공할 수 있도록 인터페이스가 잘 설계되어 있습니다. 이는 BaseErrorCode와 잘 통합되어 일관된 예외 처리 시스템을 구축하는 데 도움이 될 것입니다.

src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1)

13-19: 유틸리티 클래스 구조가 적절합니다.

유틸리티 클래스를 final로 선언하고 private 생성자를 사용한 설계가 좋습니다. 또한 HTTP 클라이언트를 싱글톤으로 관리하고 타임아웃을 상수로 정의한 점도 좋은 접근 방식입니다.

src/main/java/org/sopt/makers/global/constant/Color.java (1)

11-21: 컬러 열거형 설계가 체계적입니다.

로그 레벨에 따른 색상 매핑이 잘 정의되어 있습니다. 색상 값과 관련 로그 레벨을 함께 관리하는 접근 방식이 좋습니다. Lombok을 사용하여 getter와 생성자를 자동화한 것도 효율적입니다.

src/main/java/org/sopt/makers/global/config/ObjectMapperConfig.java (1)

1-29: 적절한 싱글톤 패턴과 ObjectMapper 설정 구현

ObjectMapper 설정을 위한 싱글톤 패턴이 적절하게 구현되었습니다. 정적 홀더 패턴을 사용하여 스레드 안전한 지연 초기화를 제공하고 있으며, ObjectMapper의 설정이 효율적으로 구성되어 있습니다.

다음 설정들이 잘 적용되어 있습니다:

  • JsonInclude.Include.NON_NULL: null 값 제외 직렬화
  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES: 알 수 없는 속성 무시
  • SerializationFeature.WRITE_DATES_AS_TIMESTAMPS: 날짜를 ISO-8601 형식으로 직렬화
// 불변성을 보장하기 위한 선택적 개선 사항(필수는 아님)
 private static ObjectMapper createObjectMapper() {
   ObjectMapper objectMapper = new ObjectMapper();
   objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
   objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
   objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+  // 설정 완료 후 불변 상태로 만들기
+  // objectMapper.findAndRegisterModules();
   return objectMapper;
 }
src/main/java/org/sopt/makers/global/exception/unchecked/InvalidSlackPayloadException.java (1)

1-13: 예외 계층 구조가 잘 설계되었습니다

Slack 페이로드 관련 예외 처리가 명확하게 구현되어 있습니다. 정적 팩토리 메서드를 통해 가독성 있는 예외 생성 방식을 제공하고 있습니다.

다만, 원인 예외를 연결할 수 있는 생성자가 있으면 더 완벽할 것 같습니다:

public class InvalidSlackPayloadException extends SentryUncheckedException {
    public InvalidSlackPayloadException(BaseErrorCode errorCode) {
        super(errorCode);
    }
    
+   public InvalidSlackPayloadException(BaseErrorCode errorCode, Throwable cause) {
+       super(errorCode, cause);
+   }

    public static InvalidSlackPayloadException from(BaseErrorCode errorCode) {
        return new InvalidSlackPayloadException(errorCode);
    }
    
+   public static InvalidSlackPayloadException from(BaseErrorCode errorCode, Throwable cause) {
+       return new InvalidSlackPayloadException(errorCode, cause);
+   }
}
src/main/java/org/sopt/makers/global/exception/checked/SlackSendException.java (1)

1-13: 명확한 예외 처리 구현

Slack 메시지 전송 오류에 대한 예외 클래스가 체계적으로 구현되어 있습니다. 체크드 예외를 통해 호출자가 명시적으로 예외를 처리하도록 강제하는 것이 적절합니다.

원인이 되는 예외를 전달할 수 있는 생성자를 추가하면 더 유용할 것 같습니다:

public class SlackSendException extends SentryCheckedException {
    public SlackSendException(BaseErrorCode errorCode) {
        super(errorCode);
    }
    
+   public SlackSendException(BaseErrorCode errorCode, Throwable cause) {
+       super(errorCode, cause);
+   }

    public static SlackSendException from(BaseErrorCode errorCode) {
        return new SlackSendException(errorCode);
    }
    
+   public static SlackSendException from(BaseErrorCode errorCode, Throwable cause) {
+       return new SlackSendException(errorCode, cause);
+   }
}
src/main/java/org/sopt/makers/global/exception/checked/SlackMessageBuildException.java (1)

1-13: 일관된 예외 처리 패턴 적용

Slack 메시지 구성 오류에 대한 예외 클래스가 일관된 패턴으로 구현되어 있습니다. 다른 예외 클래스들과 동일한 패턴을 따르고 있어 코드 일관성이 좋습니다.

다른 예외 클래스들과 마찬가지로 원인 예외를 처리할 수 있는 생성자를 추가하면 더 완성도 높은 구현이 될 것 같습니다:

public class SlackMessageBuildException extends SentryCheckedException {
    public SlackMessageBuildException(BaseErrorCode errorCode) {
        super(errorCode);
    }
    
+   public SlackMessageBuildException(BaseErrorCode errorCode, Throwable cause) {
+       super(errorCode, cause);
+   }

    public static SlackMessageBuildException from(BaseErrorCode errorCode) {
        return new SlackMessageBuildException(errorCode);
    }
    
+   public static SlackMessageBuildException from(BaseErrorCode errorCode, Throwable cause) {
+       return new SlackMessageBuildException(errorCode, cause);
+   }
}
src/main/java/org/sopt/makers/global/exception/unchecked/WebhookUrlNotFoundException.java (1)

5-13: 예외 처리 계층 구조가 잘 설계되어 있습니다.

SentryUncheckedException을 상속받아 웹훅 URL을 찾을 수 없을 때 발생하는 예외를 명확하게 정의했습니다. 정적 팩토리 메소드 from을 제공하여 가독성이 좋고 코드 재사용성이 높습니다.

src/main/java/org/sopt/makers/global/exception/checked/SentryCheckedException.java (1)

8-15: 체크 예외 처리 구조가 체계적으로 구현되었습니다.

SentryException 인터페이스를 구현하고 BaseErrorCode를 활용하여 일관된 에러 코드 관리 방식을 구축했습니다. protected 생성자를 통해 상속 구조를 명확히 하고, Lombok의 @Getter를 사용하여 불필요한 보일러플레이트 코드를 줄였습니다.

src/main/java/org/sopt/makers/dto/SentryEventDetail.java (1)

8-15: Java의 record 기능과 빌더 패턴을 적절히 활용했습니다.

DTO로 record를 사용한 것은 좋은 선택입니다. AccessLevel.PRIVATE 설정으로 빌더 접근 제어를 통해 생성 방식을 제한한 것도 좋습니다.

src/main/java/org/sopt/makers/global/exception/unchecked/UnsupportedServiceTypeException.java (1)

5-13: 서비스 타입 예외 처리 구현이 일관되게 작성되었습니다.

WebhookUrlNotFoundException과 유사한 패턴으로 구현되어 일관성 있는 코드 스타일을 유지하고 있습니다. 정적 팩토리 메소드를 사용하여 인스턴스 생성을 추상화한 것도 좋은 방법입니다.

src/main/java/org/sopt/makers/service/NotificationService.java (1)

8-21: 인터페이스 설계가 깔끔합니다.

알림 서비스를 위한 인터페이스가 잘 설계되었습니다. 메서드 시그니처와 예외 처리가 명확하게 정의되어 있고, Javadoc 주석도 적절히 작성되어 있습니다.

src/main/java/org/sopt/makers/service/NotificationServiceFactory.java (2)

13-37: 팩토리 패턴이 잘 구현되었습니다.

정적 맵을 사용한 서비스 타입별 구현체 관리가 잘 되어 있습니다. 지원하지 않는 서비스 타입에 대한 예외 처리도 적절합니다.


21-21: 디스코드 서비스 구현 계획이 있나요?

주석 처리된 DiscordNotificationService 항목이 있는데, 향후 구현 계획이 있는지 확인해 보세요. 만약 구현 계획이 있다면 TODO 주석으로 명시해두는 것이 좋을 것 같습니다.

src/main/java/org/sopt/makers/global/util/EnvUtil.java (2)

11-44: 환경 변수 처리 로직이 깔끔합니다.

서비스 타입에 따른 웹훅 URL을 환경 변수에서 가져오는 로직이 잘 구현되어 있습니다. 예외 처리와 로깅도 적절합니다.


46-52: switch 식 사용이 적절합니다.

Java의 새로운 switch 식을 사용하여 코드가 간결하고 가독성이 좋습니다. 향후 지원할 서비스가 추가될 때 이 부분만 확장하면 됩니다.

src/main/java/org/sopt/makers/service/SlackNotificationService.java (1)

32-36: ObjectMapper 싱글톤 사용 👍

DI 없이도 ObjectMapperConfig 싱글톤을 주입해 thread‑safe 하게 재사용하도록 한 점이 좋습니다.

- Slack API 요청 구조를 표현하는 Value Object 추가
- Block, Element, Text 인터페이스 및 구현체 추가
- 모든 VO는 불변 객체로 record 타입으로 구현
- 각 클래스에 newInstance 패턴의 정적 팩토리 메서드 추가
- 타입 안전성과 유지보수성 향상을 위한 클래스 구조 설계
- Map<String, Object> 데이터 구조를 타입 안전한 VO로 교체
- 메서드 반환 타입을 구체적인 VO 타입으로 변경
- JSON 키를 직접 다루는 대신 VO 객체의 정적 팩토리 메서드 활용
- 예외 처리 로직은 기존 방식 유지
- APIGatewayProxyRequestEvent 기반 Sentry 웹훅 수신 처리
- 팀, 타입, 서비스 정보 추출 및 알림 전송 처리
- 이벤트 유효성 검사 및 SentryEventDetail 추출 로직 추가
- 성공/오류 응답 포맷 JSON 형태로 구성
- checked/unchecked/custom 예외에 따른 응답 처리 분기
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

🧹 Nitpick comments (3)
src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java (3)

8-12: 레코드의 필드에 대한 Javadoc 주석 추가 권장

SectionBlock 레코드의 필드에 대한 설명이 없어 다른 개발자들이 각 필드의 목적과 사용법을 이해하기 어려울 수 있습니다. 각 필드(type, fields, text)의 역할에 대한 Javadoc 주석을 추가하는 것이 좋습니다.


1-20: 클래스 수준의 Javadoc 문서화 필요

SectionBlock 클래스의 목적과 Slack API와의 관계에 대한 설명이 없습니다. 클래스 수준의 Javadoc을 추가하여 이 클래스가 Slack의 Section Block을 표현하는 것임을 명시하고, 관련 Slack API 문서 링크를 제공하는 것이 좋습니다:

/**
 * Slack의 Section Block을 표현하는 레코드입니다.
 * Section Block은 텍스트 필드나 단일 텍스트를 포함할 수 있습니다.
 * 
 * @see <a href="https://api.slack.com/reference/block-kit/blocks#section">Slack API: Section Block</a>
 */
public record SectionBlock(...)

1-20: 메서드 명명 규칙 통일 검토

static factory 메서드의 명명 규칙이 newInstanceWithXXX 형태인데, 일반적으로 Java에서는 of, from, create 등의 접두사를 많이 사용합니다. 프로젝트 내에서 일관된 메서드 명명 규칙을 사용하는지 확인하고, 필요하다면 아래와 같이 변경을 고려해보세요:

- public static SectionBlock newInstanceWithFields(List<Text> fields)
+ public static SectionBlock withFields(List<Text> fields)

- public static SectionBlock newInstanceWithText(Text text) 
+ public static SectionBlock withText(Text text)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44d709d and c686519.

📒 Files selected for processing (10)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1 hunks)
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/SlackMessage.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/block/ActionsBlock.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/block/Block.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/element/Button.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/element/Element.java (1 hunks)
  • src/main/java/org/sopt/makers/vo/slack/text/Text.java (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/org/sopt/makers/vo/slack/element/Element.java
  • src/main/java/org/sopt/makers/vo/slack/SlackMessage.java
  • src/main/java/org/sopt/makers/vo/slack/text/Text.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java
🔇 Additional comments (7)
src/main/java/org/sopt/makers/vo/slack/block/Block.java (1)

3-5: 잘 정의된 sealed 인터페이스입니다.

sealed 인터페이스를 사용하여 구현 클래스를 HeaderBlock, SectionBlock, ActionsBlock으로 제한한 점이 좋습니다. 이를 통해 타입 안전성을 보장하고 Slack 메시지 블록의 종류를 명확하게 제한할 수 있습니다.

src/main/java/org/sopt/makers/vo/slack/block/ActionsBlock.java (2)

8-11: 잘 설계된 record 클래스입니다.

Block 인터페이스를 구현하는 ActionsBlock record 클래스가 적절하게 설계되었습니다. Slack API 형식에 맞게 type과 elements 필드를 포함하고 있습니다.


12-14: 팩토리 메서드 패턴 적용이 좋습니다.

newInstance 메서드를 통해 ActionsBlock 객체 생성을 캡슐화한 점이 좋습니다. 상수를 활용하여 코드 일관성을 유지하고 있습니다.

src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java (2)

7-10: 잘 설계된 HeaderBlock record 클래스입니다.

Block 인터페이스를 구현하는 HeaderBlock record 클래스가 적절하게 설계되었습니다. Slack API 형식에 맞게 type과 text 필드를 포함하고 있습니다.


11-13: 팩토리 메서드 패턴 적용이 좋습니다.

newInstance 메서드를 통해 HeaderBlock 객체 생성을 캡슐화하고, Text 객체 생성까지 처리하여 사용자 편의성을 높인 점이 좋습니다. 상수를 활용하여 코드 일관성을 유지하고 있습니다.

src/main/java/org/sopt/makers/vo/slack/element/Button.java (2)

6-11: 잘 설계된 Button record 클래스입니다.

Element 인터페이스를 구현하는 Button record 클래스가 적절하게 설계되었습니다. Slack API 형식에 맞게 필요한 필드들을 모두 포함하고 있습니다.


12-14: 팩토리 메서드 패턴 적용이 좋습니다.

newInstance 메서드를 통해 Button 객체 생성을 캡슐화하고, Text 객체 생성까지 처리하여 사용자 편의성을 높인 점이 좋습니다. 상수를 활용하여 코드 일관성을 유지하고 있습니다.

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

🧹 Nitpick comments (4)
src/main/java/org/sopt/makers/dto/WebhookRequest.java (1)

10-16: Builder 사용이 과도할 수 있습니다
레코드(immutability) 특성상 new WebhookRequest(stage, team, type, serviceType) 로도 충분히 간결합니다. Builder를 private 로 열어두고 내부에서만 사용하는 현재 형태는 가독성 대비 이점이 크지 않습니다. 불필요한 Lombok 의존을 줄이고 싶다면 단순 생성자 호출로 전환을 검토해 주세요.

src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1)

6-7: 유틸리티 클래스용 Lombok 애너테이션 활용 제안
@NoArgsConstructor(access = AccessLevel.PRIVATE) + final class 패턴 대신 Lombok 의 @UtilityClass 를 사용하면

  • 자동으로 private 생성자와 static 필드를 생성
  • 클래스명을 복수형(SlackConstants) 으로 변경하는 권장 스타일 반영
    할 수 있습니다.
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class SlackConstant {
+@UtilityClass
+public class SlackConstants {
src/main/java/org/sopt/makers/handler/SentryWebhookHandler.java (2)

90-97: JSON 파싱 예외 메시지 구체화 및 null 입력 대비
readTree 실패 시 동일한 INVALID_SLACK_PAYLOAD 로 매핑하는 것은 좋으나, null 과 JSON 구문 오류를 로그 구분 없이 같은 예외로 처리하고 있습니다. 로그에서 원인을 쉽게 파악하도록 메시지를 세분화하거나 Validation 단계에서 먼저 null 여부를 확인해 주세요.


179-183: 불변 헤더 캐싱으로 객체 생성 비용 절감 가능
매 요청마다 HashMap 을 새로 생성할 필요 없이, 불변 Mapstatic final 로 선언해 재사용하면 GC 부하를 줄일 수 있습니다.

-private Map<String, String> createJsonContentTypeHeaders() {
-	Map<String, String> headers = new HashMap<>();
-	headers.put(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
-	return headers;
-}
+private static final Map<String, String> JSON_HEADERS =
+	Map.of(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
+
+private Map<String, String> createJsonContentTypeHeaders() {
+	return JSON_HEADERS;
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c686519 and e56513d.

📒 Files selected for processing (4)
  • src/main/java/org/sopt/makers/dto/WebhookRequest.java (1 hunks)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1 hunks)
  • src/main/java/org/sopt/makers/handler/SentryWebhookHandler.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/org/sopt/makers/handler/SentryWebhookHandler.java (1)
src/main/java/org/sopt/makers/global/exception/unchecked/InvalidSlackPayloadException.java (1)
  • InvalidSlackPayloadException (5-13)
🔇 Additional comments (1)
src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1)

12-24: 에러 코드 정의 일관성 확인 완료
HTTP 상태, 메시지, 코드가 명확히 구분되어 있으며 향후 확장성을 고려한 설계로 보입니다. 별다른 문제 없습니다.

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

♻️ Duplicate comments (2)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (2)

82-93: Slack API 응답 검증이 개선되었습니다.

이전 리뷰에서 지적된 사항이 잘 반영되어 HTTP 상태 코드와 응답 본문을 모두 확인하고 있습니다. 성공 시 적절한 로그를 남기는 것도 좋습니다.


151-156: 날짜 포맷팅 이슈가 해결되었습니다.

이전 리뷰에서 지적된 ISO 날짜의 Z 오프셋 처리 문제가 OffsetDateTime을 사용하여 적절히 해결되었습니다. UTC에서 한국 시간으로의 변환도 정확하게 구현되어 있습니다.

🧹 Nitpick comments (1)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (1)

51-60: 예외 처리가 잘 구현되었습니다.

DateTimeException 발생 시 적절한 로깅과 함께 구체적인 예외 변환이 이루어지고 있습니다. 다만, 입력 파라미터에 대한 null 검증이 없어 NullPointerException이 발생할 가능성이 있습니다.

파라미터 유효성 검증을 추가하는 것이 좋겠습니다:

private SlackMessage createSlackMessage(String team, String type, String stage,
    SentryEventDetail sentryEventDetail) throws SentryCheckedException {
+   if (sentryEventDetail == null) {
+       log.error("Slack 메시지 생성 실패: sentryEventDetail이 null입니다.");
+       throw SlackMessageBuildException.from(ErrorMessage.SLACK_MESSAGE_BUILD_FAILED);
+   }
    try {
        return buildSlackMessage(team, type, stage, sentryEventDetail);
    } catch (DateTimeException e) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e56513d and 9918a13.

📒 Files selected for processing (3)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java (1 hunks)
  • src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1 hunks)
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/sopt/makers/global/constant/SlackConstant.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/sopt/makers/global/util/HttpClientUtil.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (3)
src/main/java/org/sopt/makers/global/exception/checked/SlackMessageBuildException.java (1)
  • SlackMessageBuildException (5-13)
src/main/java/org/sopt/makers/global/exception/checked/SlackSendException.java (1)
  • SlackSendException (5-13)
src/main/java/org/sopt/makers/service/NotificationServiceFactory.java (1)
  • Slf4j (13-38)
🔇 Additional comments (3)
src/main/java/org/sopt/makers/service/SlackNotificationService.java (3)

35-46: 잘 구조화된 클래스 설계입니다.

NotificationService 인터페이스 구현과 ObjectMapper 의존성 주입 방식이 깔끔합니다. 싱글톤 패턴을 활용해 ObjectMapper 인스턴스를 효율적으로 관리하고 있습니다.


62-80: 예외 처리가 포괄적으로 구현되었습니다.

다양한 종류의 예외를 적절히 처리하고 있으며, 특히 InterruptedException 처리 시 스레드 인터럽트 상태를 복원하는 부분이 좋습니다. 예외 발생 시 로그에 충분한 컨텍스트 정보를 포함하고 있습니다.


115-146: 블록 빌더 메서드가 잘 구성되어 있습니다.

각 블록 타입별로 별도의 빌더 메서드를 통해 코드 가독성이 향상되었습니다. 상수 사용과 포맷팅 방식이 일관성 있게 적용되어 있습니다.

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 (3)
src/main/java/org/sopt/makers/vo/slack/SlackMessage.java (3)

11-13: 팩토리 메서드 구현이 깔끔합니다.

정적 팩토리 메서드를 사용하여 객체 생성을 캡슐화한 것은 좋은 패턴입니다. 하지만 파라미터에 대한 null 체크가 없어 NullPointerException이 발생할 수 있습니다.

public static SlackMessage newInstance(List<Block> blocks, String color) {
+	if (blocks == null) {
+		blocks = List.of();
+	}
+	if (color == null) {
+		color = "#000000"; // 기본 색상 값 설정
+	}
	return new SlackMessage(List.of(Attachment.of(color, blocks)));
}

15-19: 중첩 레코드 클래스가 잘 구현되어 있습니다.

Attachment 레코드와 팩토리 메서드가 잘 구현되어 있습니다. 여기서도 마찬가지로 null 체크를 추가하는 것이 안전할 것입니다.

public static Attachment of(String color, List<Block> blocks) {
+	if (blocks == null) {
+		blocks = List.of();
+	}
+	if (color == null) {
+		color = "#000000"; // 기본 색상 값 설정
+	}
	return new Attachment(color, blocks);
}

1-20: Javadoc 문서화를 추가하는 것이 좋겠습니다.

코드의 가독성과 유지보수성을 위해 클래스와 메서드에 Javadoc 문서화를 추가하는 것을 권장합니다. 특히 color 파라미터가 어떤 형식이어야 하는지(예: hex 코드) 명확히 하면 도움이 될 것입니다.

/**
+ * Slack API에 전송할 메시지 형식을 정의하는 레코드 클래스입니다.
+ * Slack Block Kit 형식을 따릅니다.
+ */
public record SlackMessage(
	List<Attachment> attachments
) {

	/**
	 * 주어진 Block 목록과 색상으로 새로운 SlackMessage 인스턴스를 생성합니다.
	 * 
	 * @param blocks 메시지에 포함될 Block 객체 목록
	 * @param color 메시지 왼쪽에 표시될 색상 (hex 코드 형식, 예: "#FF0000")
	 * @return 새로운 SlackMessage 인스턴스
	 */
	public static SlackMessage newInstance(List<Block> blocks, String color) {
		return new SlackMessage(List.of(Attachment.of(color, blocks)));
	}

	/**
	 * 색상과 Block 목록을 포함하는 Attachment 레코드 클래스입니다.
	 */
	public record Attachment(String color, List<Block> blocks) {
		/**
		 * 주어진 색상과 Block 목록으로 새로운 Attachment 인스턴스를 생성합니다.
		 * 
		 * @param color 메시지 왼쪽에 표시될 색상 (hex 코드 형식)
		 * @param blocks 첨부 파일에 포함될 Block 객체 목록
		 * @return 새로운 Attachment 인스턴스
		 */
		public static Attachment of(String color, List<Block> blocks) {
			return new Attachment(color, blocks);
		}
	}
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c46ddac and b6b9c00.

📒 Files selected for processing (1)
  • src/main/java/org/sopt/makers/vo/slack/SlackMessage.java (1 hunks)
🔇 Additional comments (2)
src/main/java/org/sopt/makers/vo/slack/SlackMessage.java (2)

1-6: 패키지 구조와 임포트가 잘 정의되어 있습니다.

패키지 이름과 필요한 클래스들이 잘 임포트되어 있습니다. 코드가 깔끔하게 정리되어 있습니다.


7-9:

Details

❓ Verification inconclusive

Record 클래스의 사용이 적절합니다.

Java 16에서 도입된 Record 기능을 사용하여 불변 데이터 객체를 생성한 것은 좋은 접근 방식입니다. 다만, 프로젝트가 Java 16 이상으로 설정되어 있는지 확인하세요.


🏁 Script executed:

#!/bin/bash
# Java 버전 확인하기
cat pom.xml | grep -A 3 "<java.version>"

Length of output: 82


Record 클래스 사용 검토 및 Java 버전 확인 요청
레코드 사용은 적절하지만, Java 16 이상이 필요하므로 빌드 설정이 올바른지 직접 확인해 주세요.

• Gradle을 사용하는 경우
build.gradle 또는 build.gradle.kts 파일 내의 sourceCompatibility/targetCompatibility 설정 확인
• Maven을 사용하는 경우
– 최상위 또는 모듈별 pom.xml 파일 내 <java.version> 또는 <maven.compiler.source>/<maven.compiler.target> 설정 확인

@move-hoon move-hoon closed this Apr 19, 2025
@move-hoon move-hoon reopened this Apr 19, 2025
@coderabbitai
Copy link

coderabbitai bot commented Apr 19, 2025

Tip

For best results, initiate chat on the files or code changes.

@hoonyworld PR 구현 내용 잘 봤습니다! Sentry 웹훅을 받아서 Slack으로 알림을 전송하는 기능을 잘 구현하셨네요. 특히 NotificationServiceFactory를 통한 서비스 타입 분리와 환경 변수 기반 URL 조회, 그리고 예외 처리 체계가 잘 설계되어 있는 것 같습니다.

코드 구조가 확장성을 고려해서 잘 설계되어 있어서, 나중에 Discord 같은 다른 서비스로도 쉽게 확장할 수 있을 것 같습니다. 시퀀스 다이어그램으로 흐름도 명확하게 표현해주셔서 이해하기 좋네요!

혹시 이 PR에 대해 추가적인 피드백이나 질문이 필요하신 부분이 있으신가요?

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

🧹 Nitpick comments (3)
src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (2)

39-47: 성공·실패 로깅 메시지 통일 및 응답 본문 포함 권장

에러 로그에만 response.body()를 담고 있어 정상/비정상 상황에서 정보가 비대칭적입니다. 정상 응답도 debug 레벨로 기록하고, 오류 응답에서는 본문을 함께 남겨야 추후 Slack API 문서 대조가 용이합니다.


54-56: 불필요한 Exception catch는 제거

마지막 catch (Exception e) 블록은 앞선 IOException, InterruptedException을 제외한 모든 런타임 예외를 삼켜 버립니다. 예상치 못한 버그를 조기에 드러내기 위해선 제거하고 상위로 전파하는 편이 좋습니다.

src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1)

11-24: 에러 코드 관리 전략 명확화 필요

SLACK_SEND_FAILED(502)SLACK_NETWORK_ERROR(503)처럼 HTTP 상태 코드를 직접 표기하고 있지만, 내부 로직에서 실제로 어떤 상황에 매핑되는지 주석이나 Javadoc이 없습니다. 팀 차원에서 “에러 코드 ↔ 실제 발생 조건” 표를 유지하지 않으면 중복·오용 위험이 큽니다. 간단한 설명이라도 각 enum 상수 위에 달아 두는 것을 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b09bcaf and 50f54cb.

📒 Files selected for processing (4)
  • src/main/java/org/sopt/makers/global/exception/message/ErrorMessage.java (1 hunks)
  • src/main/java/org/sopt/makers/global/exception/unchecked/HttpRequestException.java (1 hunks)
  • src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1 hunks)
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/sopt/makers/service/SlackNotificationService.java
🧰 Additional context used
🧠 Learnings (1)
src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1)
Learnt from: hoonyworld
PR: sopt-makers/sentry-notifier#2
File: src/main/java/org/sopt/makers/global/util/HttpClientUtil.java:0-0
Timestamp: 2025-04-19T08:50:12.876Z
Learning: The HttpClientUtil class should throw a specific exception (HttpRequestException) that extends SentryUncheckedException rather than a generic Exception. It should also handle HTTP status codes (4xx, 5xx) appropriately, and include comprehensive logging for both success and failure cases.
🔇 Additional comments (1)
src/main/java/org/sopt/makers/global/util/HttpClientUtil.java (1)

41-44: HTTP 상태 코드별 세분화된 예외 코드 매핑 필요

현재 구현은 4xx·5xx를 모두 SLACK_NETWORK_ERROR로 매핑하고 있습니다.

  • 4xx(클라이언트 오류) → SLACK_SEND_FAILED
  • 5xx(서버 오류) → SLACK_NETWORK_ERROR 또는 별도 코드

처럼 구분하면 장애 원인 파악과 모니터링이 훨씬 수월해집니다.

- if (response.statusCode() >= 400) {
-     log.error("[HTTP 오류 응답] URL: {}, Status: {}", url, response.statusCode());
-     throw HttpRequestException.from(ErrorMessage.SLACK_NETWORK_ERROR);
- }
+ int status = response.statusCode();
+ if (status >= 400 && status < 500) {
+     log.error("[HTTP 클라이언트 오류] URL: {}, Status: {}, Body: {}", url, status, response.body());
+     throw HttpRequestException.from(ErrorMessage.SLACK_SEND_FAILED);
+ } else if (status >= 500) {
+     log.error("[HTTP 서버 오류] URL: {}, Status: {}, Body: {}", url, status, response.body());
+     throw HttpRequestException.from(ErrorMessage.SLACK_NETWORK_ERROR);
+ }

[ suggest_essential_refactor ]

@move-hoon move-hoon merged commit 5310e9a into main Apr 19, 2025
1 check passed
@move-hoon move-hoon deleted the feat/#1 branch May 12, 2025 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 기본 Sentry Webhook 수신 기능 구현

1 participant