메시징
Lumie 백엔드는 RabbitMQ를 통 한 비동기 메시징을 지원합니다. 현재는 OMR 배치 채점 워크플로우에서 사용합니다.
libs/messaging
libs/messaging에 Exchange, Queue, Routing Key 상수를 정의합니다. 메시징에 참여하는 모든 모듈이 이 상수를 공유하여 문자열 오타를 방지합니다.
// libs/messaging/src/main/java/com/lumie/messaging/config/RabbitMqConstants.java
public final class RabbitMqConstants {
// Exchange
public static final String LUMIE_COMMANDS_EXCHANGE = "lumie.commands";
public static final String LUMIE_DLX_EXCHANGE = "lumie.dlx";
// Queues — OMR 채점
public static final String GRADING_OMR_REQUEST_QUEUE = "grading.omr-request";
public static final String GRADING_OMR_REQUEST_DLQ = "grading.omr-request.dlq";
public static final String GRADING_OMR_COMPLETED_QUEUE = "grading.omr-completed";
// Routing Keys — OMR 채점
public static final String GRADING_OMR_REQUEST_ROUTING_KEY = "grading.omr.request";
public static final String GRADING_COMPLETED_ROUTING_KEY = "grading.completed";
private RabbitMqConstants() {}
}
OMR 배치 채점 흐름
OmrGradingJobMessage
// exam 모듈/application/dto/message/OmrGradingJobMessage.java
public record OmrGradingJobMessage(
Long jobId,
Long examId,
String tenantSlug, // RabbitListener 스레드에서 테넌트 컨텍스트 복원용
Long tenantId, // RLS 바인딩 핵심 — 누락 시 행이 보이지 않음
List<String> imageKeys
) implements Serializable {}
메시지에 tenantSlug와 tenantId를 모두 포함시키는 이유: @RabbitListener 스레드는 HTTP 요청 스레드가 아니므로 JwtAuthenticationFilter가 실행되지 않습니다. 리스너에서 TenantContextHolder.setTenant(slug) + TenantContextHolder.setTenantId(id) 를 모두 호출해야 RLS 정책이 정상 동작합니다.
OmrGradingJobListener
// exam 모듈/adapter/in/messaging/OmrGradingJobListener.java
@Slf4j
@Component
@RequiredArgsConstructor
public class OmrGradingJobListener {
private final OmrGradingJobProcessor processor;
private final OmrStoragePort storagePort;
@RabbitListener(queues = RabbitMqConstants.GRADING_OMR_REQUEST_QUEUE)
public void handleOmrGradingJob(OmrGradingJobMessage message) {
// 테넌트 컨 텍스트 수동 복원 (HTTP 스레드가 아니므로)
TenantContextHolder.setTenant(message.tenantSlug());
TenantContextHolder.setTenantId(message.tenantId());
try {
processor.startProcessing(message.jobId());
for (String imageKey : message.imageKeys()) {
processor.processImageAndUpdateProgress(
message.jobId(), message.examId(), imageKey, message.tenantSlug());
}
processor.completeJob(message.jobId());
storagePort.deleteObjects(message.imageKeys());
} catch (Exception e) {
processor.failJob(message.jobId());
} finally {
TenantContextHolder.clear(); // ThreadLocal 반드시 정리
}
}
}
RabbitMQ 설정
app/src/main/resources/application.yaml:
spring:
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:guest}
password: ${RABBITMQ_PASSWORD:guest}
virtual-host: /
운영 환경에서는 lumie-backend-rabbitmq-secrets Kubernetes Secret에서 자격증명을 주입받습 니다. RabbitMQ 운영자(Topology Operator)가 lumie-event 네임스페이스에 User CR을 관리합니다.
메시징 패턴 요약
| 항목 | 값 |
|---|---|
| Exchange | lumie.commands (direct) |
| Dead Letter Exchange | lumie.dlx |
| OMR 요청 Queue | grading.omr-request |
| OMR 요청 DLQ | grading.omr-request.dlq |
| OMR 완료 Queue | grading.omr-completed |
| 발행 모듈 | exam |
| 수신 모듈 | exam (자체 수신 → grading-svc HTTP 위임) |