-
Notifications
You must be signed in to change notification settings - Fork 3
feature / refactor : 채점 시스템 SSE → WebSocket 기반으로 전환 #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough이 변경사항은 코드 제출 및 채점 결과의 실시간 스트리밍 방식을 SSE에서 WebSocket(STOMP) 기반으로 전환합니다. 이를 위해 이벤트 기반 구조가 도입되어, 제출, 채점, 에러 등 다양한 이벤트가 서비스, 퍼블리셔, 리스너를 통해 처리됩니다. DTO, 엔티티, 컨트롤러, 프론트엔드 템플릿 등 관련된 모든 계층이 WebSocket 이벤트 흐름에 맞게 리팩토링되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant SubmissionController
participant SubmissionService
participant SubmissionEventService
participant SubmissionEventListener
participant StompMessageService
User->>SubmissionController: POST /problems/{id}/submit-ws
SubmissionController->>SubmissionService: enqueueCodeSubmission()
SubmissionService->>SubmissionEventService: publishInitTestcases(...)
SubmissionService->>SubmissionEventService: publishTestcaseUpdate(...)
SubmissionService->>SubmissionEventService: publishFinalResult(...)
SubmissionService->>SubmissionEventService: publishSubmissionError(...)
SubmissionEventService->>SubmissionEventListener: (Spring Event)
SubmissionEventListener->>StompMessageService: sendInitTestcases(...)
SubmissionEventListener->>StompMessageService: sendTestcaseResultUpdate(...)
SubmissionEventListener->>StompMessageService: sendFinalResult(...)
SubmissionEventListener->>StompMessageService: sendError(...)
StompMessageService-->>User: WebSocket 메시지 전송 (/topic/submission/{sessionKey}/...)
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 (10)
src/main/java/org/ezcode/codetest/infrastructure/event/config/RedisStreamConfig.java (1)
65-65: 예외 정보 로깅 레벨 변경에 대한 검토가 필요합니다.예외 정보를
log.error에서log.info로 변경했는데, 이로 인해 디버깅이 어려워질 수 있습니다. Redis Consumer Group 초기화 중 발생하는 예외가 정상적인 시나리오(이미 존재하는 그룹)인 경우가 많다면 적절하지만, 예상치 못한 오류를 놓칠 위험이 있습니다.더 명확한 로깅을 위해 다음과 같이 수정하는 것을 고려해보세요:
- log.info("예외 발생: {}, 메시지: {}", e.getClass(), e.getMessage()); + log.debug("Redis Consumer Group 초기화 중 예외 발생: {}, 메시지: {}", e.getClass(), e.getMessage());src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/SubmissionDetailResponse.java (1)
24-25: 타입 일관성 개선은 좋으나 문서 업데이트가 필요합니다.
executionTime의 타입을Long으로 변경한 것은 엔티티와의 일관성을 위해 적절합니다. 다만 스키마 설명에서 여전히 "실행 시간 (s)"로 표기되어 있는데,Long타입으로 변경된 후 단위가 여전히 초인지 밀리초로 변경되었는지 확인하고 문서를 업데이트해 주세요.스키마 설명을 다음과 같이 업데이트하는 것을 고려해 보세요:
- @Schema(description = "실행 시간 (s)", example = "0.129") + @Schema(description = "실행 시간 (ms)", example = "129")src/main/java/org/ezcode/codetest/application/submission/dto/event/TestcaseListInitializedEvent.java (1)
7-17: 이벤트 레코드 설계가 적절하며 정적 팩토리 메서드 간소화를 고려해 보세요.테스트케이스 목록 초기화를 위한 이벤트 레코드가 잘 설계되었습니다. 불변 구조와 명확한 필드명이 적절합니다.
정적 팩토리 메서드
from()이 단순히 생성자를 호출하기만 하므로, 현재 구현에서는 생략하거나 추가적인 검증 로직이 필요한 경우에만 유지하는 것을 고려해 보세요.더 간단한 구현을 원한다면 정적 팩토리 메서드를 제거할 수 있습니다:
- public static TestcaseListInitializedEvent from(String sessionKey, List<InitTestcaseListPayload> payload) { - return new TestcaseListInitializedEvent(sessionKey, payload); - }src/main/java/org/ezcode/codetest/infrastructure/event/publisher/RedisJudgeQueueProducer.java (1)
21-21: 로그 메시지가 더 이상 정확하지 않습니다.SSE에서 WebSocket으로 전환되었으므로 로그 메시지를 업데이트해야 합니다.
- log.info("[SSE enqueue] emitterKey: {}", submissionMessage.sessionKey()); + log.info("[WebSocket enqueue] sessionKey: {}", submissionMessage.sessionKey());src/main/java/org/ezcode/codetest/application/submission/dto/event/TestcaseEvaluatedEvent.java (1)
12-18: 정적 팩토리 메서드가 필요한지 검토해보세요.record의 생성자와 동일한 역할을 하는 정적 팩토리 메서드가 추가적인 가치를 제공하는지 확인해보세요. 단순히 생성자 호출을 래핑하는 것이라면 불필요할 수 있습니다.
- public static TestcaseEvaluatedEvent of(String sessionKey, TestcaseResultPayload payload) { - return new TestcaseEvaluatedEvent( - sessionKey, - payload - ); - }대신 직접 생성자를 사용하는 것을 고려해보세요:
new TestcaseEvaluatedEvent(sessionKey, payload)src/main/java/org/ezcode/codetest/domain/submission/model/SubmissionAggregator.java (1)
17-19: 평균 계산에서 정밀도 손실 가능성 검토정수 나눗셈으로 변경되면서 소수점 이하 값이 버려질 수 있습니다. 실행 시간 평균에서 정밀도가 중요한지 비즈니스 요구사항을 확인해보세요.
만약 정밀도가 중요하다면 다음과 같이 개선할 수 있습니다:
-public long averageExecutionTime() { - return count == 0 ? 0L : totalExecutionTime / count; -} +public long averageExecutionTime() { + return count == 0 ? 0L : Math.round((double) totalExecutionTime / count); +}src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/InitTestcaseListPayload.java (1)
28-28: 하드코딩된 상태값을 상수로 분리하세요."채점 중" 문자열이 하드코딩되어 있습니다. 상태값을 상수나 enum으로 관리하여 유지보수성을 향상시키는 것이 좋겠습니다.
+ private static final String GRADING_STATUS = "채점 중"; + public record InitTestcaseListPayload( // ... ) { public static List<InitTestcaseListPayload> from(ProblemInfo info) { return IntStream.rangeClosed(1, info.getTestcaseCount()) .mapToObj(seq -> { Testcase tc = info.testcaseList().get(seq - 1); return new InitTestcaseListPayload( seq, tc.getInput(), tc.getOutput(), - "채점 중" + GRADING_STATUS ); }) .toList(); } }src/main/java/org/ezcode/codetest/presentation/submission/SubmissionController.java (1)
65-69: 반환 타입을 단순화하세요.단일 세션 키를 반환하기 위해
HashSet<String>을 사용하는 것은 부자연스럽습니다. 더 명확하고 직관적인 반환 타입을 사용하는 것이 좋겠습니다.- public ResponseEntity<HashSet<String>> submitCodeStream( + public ResponseEntity<Map<String, String>> submitCodeStream( @Parameter(description = "제출할 문제 ID", required = true) @PathVariable Long problemId, @RequestBody @Valid CodeSubmitRequest request, @AuthenticationPrincipal AuthUser authUser ) { - HashSet<String> set = new HashSet<>(); - set.add(submissionService.enqueueCodeSubmission(problemId, request, authUser)); - return ResponseEntity - .status(HttpStatus.OK) - .body(set); + String sessionKey = submissionService.enqueueCodeSubmission(problemId, request, authUser); + return ResponseEntity + .status(HttpStatus.OK) + .body(Map.of("sessionKey", sessionKey)); }src/main/java/org/ezcode/codetest/infrastructure/event/publisher/SubmissionEventPublisher.java (1)
19-37: 디버깅을 위한 로깅 추가를 고려해보세요.이벤트 발행 시점을 추적하기 위한 디버그 레벨 로깅을 추가하는 것을 고려해보세요. 특히 WebSocket 기반 실시간 통신에서는 이벤트 흐름 추적이 중요할 수 있습니다.
+ import lombok.extern.slf4j.Slf4j; + @Slf4j @Component @RequiredArgsConstructor public class SubmissionEventPublisher implements SubmissionEventService { @Override public void publishInitTestcases(TestcaseListInitializedEvent event) { + log.debug("Publishing testcase list initialized event for session: {}", event.sessionKey()); publisher.publishEvent(event); } // 다른 메소드들도 유사하게 로깅 추가src/main/java/org/ezcode/codetest/infrastructure/event/publisher/StompMessageService.java (1)
75-105: WebSocket 메시지 전송 메서드들이 잘 구현되었습니다각 이벤트 타입별로 명확한 토픽 구조를 가지고 있어 클라이언트에서 구독하기 쉽습니다. 다만, sessionKey의 유효성 검증이나 예외 처리를 추가하면 더 안정적일 것 같습니다.
sessionKey 검증을 추가하면 더 안정적일 것입니다:
public void sendInitTestcases(String sessionKey, List<InitTestcaseListResponse> dataList) { + if (sessionKey == null || sessionKey.isBlank()) { + log.warn("Invalid sessionKey provided for sendInitTestcases"); + return; + } messagingTemplate.convertAndSend( "/topic/submission/" + sessionKey + "/init", dataList ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (44)
src/main/java/org/ezcode/codetest/application/submission/dto/event/SubmissionErrorEvent.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/SubmissionJudgingFinishedEvent.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/TestcaseEvaluatedEvent.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/TestcaseListInitializedEvent.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/InitTestcaseListPayload.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/SubmissionFinalResultPayload.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/TestcaseResultPayload.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/FinalResultResponse.java(0 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/JudgeResultResponse.java(0 hunks)src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/SubmissionDetailResponse.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/model/JudgeResult.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/model/SubmissionContext.java(2 hunks)src/main/java/org/ezcode/codetest/application/submission/port/EmitterStore.java(0 hunks)src/main/java/org/ezcode/codetest/application/submission/port/ProblemEventService.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/port/QueueProducer.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/port/SubmissionEventService.java(1 hunks)src/main/java/org/ezcode/codetest/application/submission/service/SubmissionService.java(4 hunks)src/main/java/org/ezcode/codetest/domain/submission/dto/AnswerEvaluation.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/dto/SubmissionData.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/dto/SubmitProcessResult.java(0 hunks)src/main/java/org/ezcode/codetest/domain/submission/exception/code/SubmissionExceptionCode.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/model/SubmissionAggregator.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/model/TestcaseEvaluationInput.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/model/entity/Submission.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java(1 hunks)src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java(4 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/config/RedisStreamConfig.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/SubmissionMessage.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/ErrorWsResponse.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/InitTestcaseListResponse.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/JudgeResultResponse.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/SubmissionFinalResultResponse.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/listener/RedisJudgeQueueConsumer.java(2 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/listener/SubmissionEventListener.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/publisher/ProblemEventPublisher.java(2 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/publisher/RedisJudgeQueueProducer.java(2 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/publisher/StompMessageService.java(2 hunks)src/main/java/org/ezcode/codetest/infrastructure/event/publisher/SubmissionEventPublisher.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/judge0/Judge0ResponseMapper.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIMessageBuilder.java(2 hunks)src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/sse/InMemoryEmitterStore.java(0 hunks)src/main/java/org/ezcode/codetest/presentation/submission/SubmissionController.java(3 hunks)src/main/resources/templates/submit-test.html(1 hunks)
💤 Files with no reviewable changes (5)
- src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/FinalResultResponse.java
- src/main/java/org/ezcode/codetest/domain/submission/dto/SubmitProcessResult.java
- src/main/java/org/ezcode/codetest/application/submission/port/EmitterStore.java
- src/main/java/org/ezcode/codetest/application/submission/dto/response/submission/JudgeResultResponse.java
- src/main/java/org/ezcode/codetest/infrastructure/sse/InMemoryEmitterStore.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (51)
src/main/java/org/ezcode/codetest/application/submission/port/ProblemEventService.java (1)
5-9: 인터페이스 설계가 잘 되어 있습니다.단일 책임 원칙을 따르며, 이벤트 발행을 위한 명확한 메서드 시그니처를 제공합니다. SSE에서 WebSocket 기반 아키텍처로의 전환에 적합한 설계입니다.
src/main/java/org/ezcode/codetest/application/submission/port/QueueProducer.java (1)
3-3: 패키지 경로 변경에 따른 일관성 확인이 필요합니다.
SubmissionMessage의 패키지 경로가 변경되고emitterKey에서sessionKey로 필드명도 변경되었다고 하니, 전체 코드베이스에서 해당 클래스를 사용하는 모든 곳이 일관되게 업데이트되었는지 확인해야 합니다.다음 스크립트를 실행하여 모든 참조가 올바르게 업데이트되었는지 확인하세요:
#!/bin/bash # SubmissionMessage 클래스 사용처 확인 rg -A 3 "SubmissionMessage" --type java # emitterKey 사용이 남아있는지 확인 rg "emitterKey" --type java # sessionKey 사용 확인 rg "sessionKey" --type javasrc/main/java/org/ezcode/codetest/infrastructure/event/publisher/ProblemEventPublisher.java (1)
13-13: 인터페이스 구현이 올바르게 되어 있습니다.
ProblemEventService인터페이스를 정확히 구현하고 있으며, 기존 로직은 그대로 유지하면서 이벤트 기반 아키텍처에 적합하게 리팩토링되었습니다.src/main/java/org/ezcode/codetest/application/submission/model/JudgeResult.java (1)
10-10: 실행 시간 타입 변경으로 인한 정밀도 손실 검토가 필요합니다.
executionTime필드가double에서long으로 변경되었는데, 이는 소수점 정밀도를 잃을 수 있습니다. 실행 시간이 밀리초 단위로 측정되고 있다면 문제없지만, 더 정밀한 측정이 필요한 경우 데이터 손실이 발생할 수 있습니다.다음 스크립트를 실행하여 관련 클래스들의 타입 일관성을 확인하세요:
#!/bin/bash # executionTime 필드를 사용하는 모든 클래스 확인 rg -A 5 -B 5 "executionTime" --type java # JudgeResult를 사용하는 코드에서 타입 호환성 확인 ast-grep --pattern 'new JudgeResult($$$)'src/main/java/org/ezcode/codetest/infrastructure/event/listener/RedisJudgeQueueConsumer.java (2)
8-8: 패키지 경로 변경 확인됨SubmissionMessage의 패키지 경로가 새로운 이벤트 기반 구조에 맞게 변경되었습니다.
30-30: emitterKey에서 sessionKey로 일관성 있게 변경됨SSE 기반 시스템에서 WebSocket 기반 시스템으로 전환하면서 "emitterKey"를 "sessionKey"로 변경한 것이 적절합니다. 변경사항이 일관성 있게 적용되었습니다.
다른 관련 클래스들에서도 동일하게 변경되었는지 확인해보겠습니다:
#!/bin/bash # Description: emitterKey 사용이 남아있는지 확인하고 sessionKey로 변경되었는지 검증 # emitterKey 사용 확인 echo "=== emitterKey 사용 확인 ===" rg -A 3 -B 3 "emitterKey" echo -e "\n=== sessionKey 사용 확인 ===" rg -A 3 -B 3 "sessionKey"Also applies to: 38-38
src/main/java/org/ezcode/codetest/domain/submission/exception/code/SubmissionExceptionCode.java (1)
20-20: 알 수 없는 오류 처리를 위한 예외 코드 추가새로운 이벤트 기반 아키텍처에서 일반적인 예외를 처리하기 위한 UNKNOWN_ERROR 코드 추가가 적절합니다. HTTP 500 상태 코드와 명확한 에러 메시지가 설정되어 있습니다.
src/main/java/org/ezcode/codetest/domain/submission/dto/SubmissionData.java (1)
30-30: 실행 시간 타입의일관성 있는 변경
executionTime필드를double에서long으로 변경한 것이 적절합니다. 실행 시간은 일반적으로 밀리초 단위의 정수로 표현하는 것이 더 정확하고 일관성 있습니다.다른 관련 클래스들에서도 실행 시간 타입이 일관성 있게 변경되었는지 확인해보겠습니다:
#!/bin/bash # Description: 실행 시간 관련 필드들의 타입 일관성 확인 # executionTime 필드를 가진 클래스들 검색 echo "=== executionTime 필드 타입 확인 ===" rg -A 2 -B 2 "executionTime" # 혹시 남아있는 double 타입 실행시간 필드 확인 echo -e "\n=== double 타입 실행시간 필드 확인 ===" rg -A 2 -B 2 "double.*[Ee]xecution[Tt]ime|[Ee]xecution[Tt]ime.*double"src/main/java/org/ezcode/codetest/domain/submission/model/TestcaseEvaluationInput.java (1)
16-16: 실행 시간 타입 통일다른 도메인 클래스들과 일관성을 유지하기 위해
executionTime타입을long으로 변경한 것이 적절합니다. 이는 시스템 전반의 데이터 타입 일관성을 개선합니다.src/main/java/org/ezcode/codetest/domain/submission/dto/AnswerEvaluation.java (1)
6-18: DTO 단순화를 통한 관심사 분리
expectedOutput과actualOutput필드를 제거하고 평가 결과만 유지하도록 단순화한 것이 적절합니다. 새로운 이벤트 기반 아키텍처에서는 출력 데이터가 이벤트 페이로드나 응답 DTO에서 처리되므로, 이 클래스는 순수하게 평가 결과만 담당하게 되어 관심사 분리가 개선되었습니다.src/main/java/org/ezcode/codetest/domain/submission/model/entity/Submission.java (1)
54-54: 실행 시간 데이터 타입 일관성 개선이 적절합니다.
executionTime필드와 생성자 매개변수의 타입을Double에서Long으로 변경한 것이 코드베이스 전반의 타입 일관성을 높이는 좋은 개선사항입니다.다만 기존 데이터와의 호환성을 확인해 주세요.
다음 스크립트로 관련 타입 변경이 일관되게 적용되었는지 확인하세요:
#!/bin/bash # Description: executionTime 필드의 타입 일관성 확인 # executionTime 관련 모든 사용처 검索 rg -A 3 -B 3 "executionTime.*Double" # 데이터베이스 스키마 변경사항 확인 fd -e sql | xargs rg -l "execution_time"Also applies to: 61-61
src/main/java/org/ezcode/codetest/application/submission/dto/event/SubmissionJudgingFinishedEvent.java (1)
5-12: 이벤트 레코드 설계가 우수합니다.WebSocket 기반 이벤트 드리븐 아키텍처로의 전환을 위한 이벤트 레코드가 잘 설계되었습니다.
record를 사용한 불변 데이터 구조와 명확한 필드명(sessionKey,payload)이 적절합니다.src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java (1)
15-17: 저장소 인터페이스 개선이 우수합니다.메서드들이
void대신UserProblemResult를 반환하도록 변경된 것은 다음과 같은 장점이 있습니다:
- JPA 저장소 관례에 부합
- 함수형 프로그래밍 패턴 지원
- 메서드 체이닝 가능
- 저장/업데이트된 엔티티에 대한 접근 제공
이러한 변경사항은 코드의 유연성과 가독성을 향상시킵니다.
src/main/java/org/ezcode/codetest/infrastructure/event/publisher/RedisJudgeQueueProducer.java (2)
6-6: LGTM! 패키지 재구성이 적절하게 반영되었습니다.새로운 패키지 구조가 이벤트 기반 아키텍처를 잘 반영하고 있습니다.
23-23: LGTM! emitterKey에서 sessionKey로의 일관된 네이밍 변경이 잘 적용되었습니다.WebSocket 기반 아키텍처에 맞는 명확한 네이밍입니다.
src/main/java/org/ezcode/codetest/infrastructure/judge0/Judge0ResponseMapper.java (4)
15-15: LGTM! 실행 시간을 밀리초 단위로 일관되게 변환하는 좋은 개선입니다.시스템 전반에서 시간 표현의 일관성을 보장합니다.
23-34: LGTM! 출력 정규화 로직이 잘 통합되었습니다.각 출력 소스별로 적절한 처리가 이루어지고 있습니다.
42-44: LGTM! 시간 변환 로직이 정확합니다.
Math.round()를 사용하여 정확한 반올림 처리가 되고 있습니다.
46-50: LGTM! 출력 정규화 로직이 적절합니다.trailing newline과 공백을 제거하여 일관된 출력 형식을 보장합니다.
src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/SubmissionMessage.java (2)
1-1: LGTM! 패키지 재구성이 적절합니다.submission 관련 DTO들을 별도 패키지로 분리하여 구조가 더 명확해졌습니다.
5-5: LGTM! WebSocket 아키텍처에 맞는 네이밍 변경입니다.
emitterKey에서sessionKey로 변경하여 의미가 더 명확해졌습니다.src/main/java/org/ezcode/codetest/application/submission/dto/event/TestcaseEvaluatedEvent.java (1)
5-11: LGTM! 이벤트 기반 아키텍처에 적합한 깔끔한 record 설계입니다.불변 객체로 설계되어 있고 필드 구성이 명확합니다.
src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/SubmissionFinalResultPayload.java (2)
5-15: LGTM! 불변 설계와 명확한 필드 구성이 우수합니다.final 필드와 Lombok을 적절히 활용한 깔끔한 설계입니다.
16-21: LGTM! 정확성 판단 로직이 적절합니다.
totalCount == passedCount로 정확성을 판단하는 비즈니스 로직이 명확하고 직관적입니다.src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIMessageBuilder.java (3)
17-17: 프롬프트 포맷팅 지시사항 개선이 적절합니다볼드체 변환 지시사항 추가로 WebSocket을 통해 전달되는 AI 응답의 일관성이 향상될 것입니다.
67-74: 명시적 개행 문자 추가로 포맷팅 일관성 확보정답 케이스에서 명시적
\n문자 추가와 탭 들여쓰기 지시사항으로 응답 포맷이 더욱 일관되게 생성될 것입니다.
81-84: 오답 케이스 포맷팅 개선 확인오답 케이스에서도 일관된 포맷팅 지시사항이 적용되어 전체적인 응답 품질이 향상되었습니다.
src/main/java/org/ezcode/codetest/application/submission/model/SubmissionContext.java (2)
3-3: 이벤트 기반 아키텍처에 맞는 DTO 변경 확인
FinalResultResponse에서SubmissionFinalResultPayload로의 변경이 SSE에서 WebSocket 전환에 적절하게 대응됩니다.
36-41: 반환 타입 변경 및 제거된 메서드 확인 필요
toFinalResult메서드의 반환 타입이 새로운 payload 클래스로 올바르게 변경되었습니다. 다만getProcessedCount()메서드가 제거되었는데, 다른 곳에서 사용되고 있지 않은지 확인이 필요합니다.다음 스크립트로 제거된
getProcessedCount()메서드의 사용처를 확인해주세요:#!/bin/bash # 제거된 getProcessedCount() 메서드의 사용처 확인 rg -A 3 "getProcessedCount" --type javasrc/main/java/org/ezcode/codetest/domain/submission/model/SubmissionAggregator.java (1)
5-5: 실행 시간 데이터 타입 일관성 개선 확인
double에서long으로의 타입 변경이 시스템 전반의 타입 일관성을 향상시킵니다.Also applies to: 11-11
src/main/java/org/ezcode/codetest/application/submission/port/SubmissionEventService.java (1)
8-17: 이벤트 기반 아키텍처를 위한 잘 설계된 인터페이스제출 과정의 각 단계별 이벤트를 명확하게 분리하여 정의했으며, 메서드명이 직관적이고 책임이 명확히 구분되어 있습니다. SSE에서 WebSocket으로의 전환에 적합한 추상화입니다.
src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/ErrorWsResponse.java (1)
7-23: WebSocket 에러 응답을 위한 잘 구조화된 DTORecord 클래스 사용과 Swagger 문서화, 그리고
SubmissionExceptionCode로부터의 정적 팩토리 메서드까지 모든 구성 요소가 적절합니다. WebSocket 기반 에러 처리에 필요한 정보를 완전히 포함하고 있습니다.src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/TestcaseResultPayload.java (1)
6-30: 잘 설계된 페이로드 레코드입니다.테스트케이스 결과를 캡슐화하는 레코드가 깔끔하게 구현되었습니다. 정적 팩토리 메서드
fromEvaluation을 통해JudgeResult와AnswerEvaluation데이터를 적절히 결합하고 있으며, 실행 시간을long타입으로 사용하는 것도 적절합니다.src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/SubmissionFinalResultResponse.java (1)
7-30: 잘 구현된 WebSocket 응답 DTO입니다.Swagger 어노테이션을 통한 API 문서화가 잘 되어 있고, 정적 팩토리 메서드를 통해 페이로드에서 응답 DTO로의 변환이 깔끔하게 구현되었습니다. SSE에서 WebSocket 기반으로의 전환에 적합한 구조입니다.
src/main/java/org/ezcode/codetest/infrastructure/event/listener/SubmissionEventListener.java (1)
19-48: 이벤트 기반 아키텍처가 잘 구현되었습니다.Spring의
@EventListener를 활용하여 제출 관련 이벤트들을 WebSocket 메시징으로 변환하는 구조가 깔끔합니다. 각 이벤트 핸들러가 단일 책임을 가지고 있으며, 페이로드를 응답 DTO로 변환하여 WebSocket을 통해 전송하는 로직이 명확합니다. SSE에서 WebSocket으로의 전환 목적에 적합한 설계입니다.src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java (1)
26-34: 리포지토리 메서드 시그니처가 개선되었습니다.저장/수정 메서드가 엔티티를 반환하도록 변경된 것은 좋은 개선입니다. 이를 통해 호출자가 관리 상태의 엔티티(예: 생성된 ID, 업데이트된 타임스탬프 등)에 접근할 수 있어 더 나은 사용성을 제공합니다. 일반적인 리포지토리 패턴에 잘 부합합니다.
src/main/java/org/ezcode/codetest/application/submission/dto/event/payload/InitTestcaseListPayload.java (1)
21-24: 스크립트가 결과를 반환하지 않아, ripgrep을 이용해 보다 폭넓게ProblemInfo구현부를 확인해 보겠습니다. 아래 스크립트를 실행해주세요.#!/bin/bash # 1. ProblemInfo 클래스 선언 위치 확인 rg -n 'class ProblemInfo' -A 20 # 2. getTestcaseCount() 메서드 구현부 검색 rg -n 'getTestcaseCount\s*\(' -A 5 src/main/java # 3. testcaseList() 메서드 구현부 검색 rg -n 'testcaseList\s*\(' -A 5 src/main/javasrc/main/java/org/ezcode/codetest/presentation/submission/SubmissionController.java (1)
44-53: API 문서가 잘 작성되었습니다.WebSocket 구독 경로와 사용법이 명확히 문서화되어 있어 클라이언트 개발자가 쉽게 이해할 수 있습니다.
src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java (3)
31-58: 함수형 스타일로의 리팩토링이 우수합니다.
Optional의map과orElseGet을 사용한 함수형 스타일 구현이 깔끔하고 읽기 좋습니다. 메소드가UserProblemResult를 반환하도록 변경된 것도 호출하는 쪽에서 결과를 활용하기에 더 유용합니다.
91-91: 실행 시간 타입 변경이 일관성을 개선합니다.
executionTime을double에서long으로 변경한 것은 밀리초 단위의 정수 표현으로 타입 일관성을 향상시킵니다.
112-118: 도메인 서비스 메소드들의 반환 타입 개선.
createUserProblemResult와modifyUserProblemResult메소드가 생성/수정된 엔티티를 반환하도록 변경된 것은 호출자가 결과를 즉시 사용할 수 있어 유용합니다.src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/JudgeResultResponse.java (2)
8-30: WebSocket 응답 DTO가 잘 설계되었습니다.
@Schema어노테이션을 통한 API 문서화가 예시와 함께 명확하게 작성되어 있고, record 구조로 불변성이 보장됩니다. 필드명과 타입도 적절합니다.
31-40: 정적 팩토리 메소드 구현이 우수합니다.
TestcaseResultPayload에서JudgeResultResponse로의 변환 로직이 깔끔하게 캡슐화되어 있습니다. 이러한 패턴은 변환 로직의 재사용성과 가독성을 높입니다.src/main/java/org/ezcode/codetest/infrastructure/event/publisher/SubmissionEventPublisher.java (1)
13-39: 이벤트 퍼블리셔가 깔끔하게 구현되었습니다.Spring의
ApplicationEventPublisher를 활용한 어댑터 패턴이 잘 적용되었습니다. 각 이벤트 타입별로 메소드가 명확히 분리되어 있어 역할이 명확합니다.src/main/java/org/ezcode/codetest/application/submission/service/SubmissionService.java (4)
76-91: 세션 키 생성 로직이 적절합니다사용자 ID와 UUID를 조합한 세션 키 생성 방식은 충돌 가능성이 매우 낮고, WebSocket 세션 식별에 적합합니다.
116-118: 타임아웃 값 증가에 대한 설명이 필요합니다타임아웃이 60초에서 100초로 증가했습니다. 이는 복잡한 문제나 다중 테스트케이스 처리를 위한 것으로 보이나, 구체적인 이유를 문서화하면 좋겠습니다.
107-122: 이벤트 기반 아키텍처 전환이 잘 구현되었습니다SSE에서 WebSocket 이벤트 기반으로의 전환이 깔끔하게 처리되었습니다. 초기화, 진행 상황, 최종 결과의 이벤트 발행 흐름이 명확합니다.
163-191: 비동기 테스트케이스 실행 로직이 적절합니다seqId 추가로 테스트케이스별 추적이 가능해졌고, 이벤트 발행을 통한 실시간 업데이트가 잘 구현되었습니다.
src/main/java/org/ezcode/codetest/infrastructure/event/dto/submission/response/InitTestcaseListResponse.java (1)
9-32: WebSocket 응답 DTO가 잘 설계되었습니다Java record를 활용한 불변 DTO 설계가 적절하고, Swagger 문서화도 충실합니다. 팩토리 메서드들이 간결하고 명확합니다.
src/main/resources/templates/submit-test.html (2)
40-48: 전역 변수 사용이 적절합니다
stompClient와sessionKey를 전역 변수로 관리하는 것은 단일 페이지 애플리케이션에서 적절한 선택입니다.
110-115: 테스트케이스 업데이트 UI가 잘 구현되었습니다각 테스트케이스의 상태에 따라 CSS 클래스를 변경하고 실행 시간과 메모리 사용량을 표시하는 로직이 명확합니다.
| private static SubmissionExceptionCode resolveCode(Throwable t) { | ||
| if (t instanceof SubmissionException se) { | ||
| return (SubmissionExceptionCode) se.getResponseCode(); | ||
| } | ||
| return SubmissionExceptionCode.UNKNOWN_ERROR; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안전하지 않은 캐스팅을 수정해주세요.
19번 라인의 (SubmissionExceptionCode) se.getResponseCode() 캐스팅이 안전하지 않습니다. getResponseCode()가 SubmissionExceptionCode를 반환한다는 보장이 없어 ClassCastException이 발생할 수 있습니다.
다음과 같이 안전한 캐스팅으로 수정하는 것을 권장합니다:
- private static SubmissionExceptionCode resolveCode(Throwable t) {
+ private static SubmissionExceptionCode resolveCode(Throwable t) {
if (t instanceof SubmissionException se) {
- return (SubmissionExceptionCode) se.getResponseCode();
+ var responseCode = se.getResponseCode();
+ return responseCode instanceof SubmissionExceptionCode ?
+ (SubmissionExceptionCode) responseCode : SubmissionExceptionCode.UNKNOWN_ERROR;
}
return SubmissionExceptionCode.UNKNOWN_ERROR;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private static SubmissionExceptionCode resolveCode(Throwable t) { | |
| if (t instanceof SubmissionException se) { | |
| return (SubmissionExceptionCode) se.getResponseCode(); | |
| } | |
| return SubmissionExceptionCode.UNKNOWN_ERROR; | |
| } | |
| private static SubmissionExceptionCode resolveCode(Throwable t) { | |
| if (t instanceof SubmissionException se) { | |
| var responseCode = se.getResponseCode(); | |
| return responseCode instanceof SubmissionExceptionCode ? | |
| (SubmissionExceptionCode) responseCode : SubmissionExceptionCode.UNKNOWN_ERROR; | |
| } | |
| return SubmissionExceptionCode.UNKNOWN_ERROR; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/application/submission/dto/event/SubmissionErrorEvent.java
lines 17 to 22, the cast on line 19 from se.getResponseCode() to
SubmissionExceptionCode is unsafe and may cause ClassCastException. To fix this,
avoid direct casting and instead check if the returned object is an instance of
SubmissionExceptionCode before casting, or modify the method to return
SubmissionExceptionCode directly if possible, ensuring type safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
나중에 SubmissionException으로 JudgeClient에서 다 랩핑해서 넘길 거야
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thezz9 아, 그렇다면 현재 코드는 그대로 두시고 향후 JudgeClient에서 모든 예외를 SubmissionException으로 래핑해서 넘겨주시면 이 캐스팅 문제가 자연스럽게 해결되겠네요! 👍
✏️ Learnings added
Learnt from: thezz9
PR: ezcode-my/backend#98
File: src/main/java/org/ezcode/codetest/application/submission/dto/event/SubmissionErrorEvent.java:17-22
Timestamp: 2025-06-25T01:24:30.754Z
Learning: JudgeClient에서 모든 예외를 SubmissionException으로 래핑해서 넘길 계획이므로, SubmissionErrorEvent.resolveCode() 메서드의 캐스팅은 안전할 예정임.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| stompClient.connect( | ||
| {}, | ||
| () => { | ||
| const base = `/topic/submission/${sessionKey}`; | ||
| stompClient.subscribe(`${base}/init`, msg => handleInit(JSON.parse(msg.body))); | ||
| stompClient.subscribe(`${base}/case`, msg => handleCase(JSON.parse(msg.body))); | ||
| stompClient.subscribe(`${base}/final`, msg => handleFinal(JSON.parse(msg.body))); | ||
| }, | ||
| err => console.error('STOMP 연결 오류:', err) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 이벤트 구독이 누락되었습니다
서버에서 /error 토픽으로 에러 메시지를 전송하지만, 클라이언트에서 구독하지 않고 있습니다.
에러 처리를 위한 구독을 추가하세요:
stompClient.connect(
{},
() => {
const base = `/topic/submission/${sessionKey}`;
stompClient.subscribe(`${base}/init`, msg => handleInit(JSON.parse(msg.body)));
stompClient.subscribe(`${base}/case`, msg => handleCase(JSON.parse(msg.body)));
stompClient.subscribe(`${base}/final`, msg => handleFinal(JSON.parse(msg.body)));
+ stompClient.subscribe(`${base}/error`, msg => handleError(JSON.parse(msg.body)));
},
err => console.error('STOMP 연결 오류:', err)
);에러 핸들러도 추가해야 합니다:
function handleError(error) {
const errorDiv = document.createElement('div');
errorDiv.style.color = 'red';
errorDiv.textContent = `오류 발생: ${error.message || '알 수 없는 오류'}`;
judgeEl.appendChild(errorDiv);
if (stompClient && stompClient.connected) {
stompClient.disconnect();
}
}🤖 Prompt for AI Agents
In src/main/resources/templates/submit-test.html around lines 87 to 96, the
client is missing a subscription to the server's /error topic for error
messages. Add a stompClient.subscribe call for the
/topic/submission/${sessionKey}/error topic inside the connect callback, and
pass a handler that parses the message and calls a new handleError function.
Implement the handleError function as described, creating a red-colored div
displaying the error message, appending it to judgeEl, and disconnecting
stompClient if connected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오키~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thezz9 네! 에러 처리 추가하시면 WebSocket 통신이 더 안정적으로 작동할 거예요. 구현하시다가 궁금한 점 있으시면 언제든 말씀해 주세요! 😊
작업 내용
참고 사항
Summary by CodeRabbit