본문으로 건너뛰기

알림 서비스

이 페이지는 발송 요청, 발송 이력, 반 단위 수신자 확장, 재사용 가능한 SMS 템플릿을 담당하는 tenant 범위 SMS 모듈 lumie-backend/modules/notification의 레퍼런스입니다.

소스 경로

Path역할
lumie-backend/modules/notification/src/main/java/com/lumie/notification/adapter/in/web/{SmsController,SmsTemplateController}.java공개 HTTP surface
lumie-backend/modules/notification/src/main/java/com/lumie/notification/application/service/{SmsCommandService,SmsQueryService}.java발송, 목록, 이력, 템플릿 로직
lumie-backend/modules/notification/src/main/java/com/lumie/notification/domain/entity/{SmsMessage,SmsTemplate}.java주요 notification aggregate
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sqlsms_messages, sms_templates의 source-of-truth table 생성과 RLS

공개 인터페이스

Endpoint목적
POST /v1/sms/send명시적인 수신자 목록으로 SMS 발송
GET /v1/sms/history, GET /v1/sms/history/{id}페이지네이션된 SMS 이력과 개별 발송 조회
POST /v1/sms/send/class/{classId}class enrollment에서 수신자를 확장한 뒤 한 번에 발송
GET /v1/sms-templates, POST /v1/sms-templates, PATCH /v1/sms-templates/{id}, DELETE /v1/sms-templates/{id}SMS 템플릿 CRUD

두 발송 endpoint에서는 Idempotency-Key가 선택 사항입니다. 값이 있으면 컨트롤러가 IdempotencyService.executeOnce(...)로 요청을 감쌉니다.

내부 인터페이스와 의존성

Dependency역할
StaffService직접 발송과 class 대상 발송 모두에서 sender를 검증
ClassServiceclass 존재 여부를 검증하고 등록된 student ID를 해석
StudentServicestudent ID를 이름과 전화번호로 확장

현재 코드베이스에는 공개된 libs/internal-api notification 계약이 없습니다.

집계와 테이블

Aggregate참고
SmsMessagesender, recipient type, JSON recipient list, 내용, 상태, 성공/실패 수를 저장
SmsTemplate재사용 가능한 템플릿 이름, 내용, 카테고리를 저장

런타임 흐름

계약 참고 사항

이 모듈은 현재 외부 SMS gateway를 호출하는 대신 발송을 즉시 성공 처리된 것으로 기록합니다.

// lumie-backend/modules/notification/src/main/java/com/lumie/notification/application/service/SmsCommandService.java
SmsMessage message = SmsMessage.create(...);

// Mock send: mark as SENT immediately
message.markAsSent();

따라서 SmsMessage.successCountfailCount는 provider가 확인한 실제 전달 결과가 아니라 프로세스 내부 mock 동작을 반영합니다.

예시 계약

이 예시는 SmsController, SendSmsRequest, RecipientRequest, SmsMessageResponse, SendToClassRequest, PageResponse에서 직접 가져왔습니다.

SMS 발송

POST /v1/sms/send
Idempotency-Key: sms-20260614-01
Content-Type: application/json

{
"senderId": 7,
"recipientType": "CUSTOM",
"recipients": [
{
"phone": "01012345678",
"name": "Kim Student",
"studentId": 101
}
],
"title": "Class reminder",
"content": "Tomorrow's class starts at 10:00.",
"templateId": 3
}
HTTP/1.1 201 Created

{
"id": 55,
"senderId": 7,
"recipientType": "CUSTOM",
"recipients": [
{
"phone": "01012345678",
"name": "Kim Student",
"studentId": 101
}
],
"title": "Class reminder",
"content": "Tomorrow's class starts at 10:00.",
"templateId": 3,
"sentAt": "<timestamp>",
"status": "SENT",
"successCount": 1,
"failCount": 0,
"createdAt": "<timestamp>",
"updatedAt": "<timestamp>"
}

이력 페이지

GET /v1/sms/history?page=0&size=20&status=SENT
HTTP/1.1 200 OK

{
"items": [
{
"id": 55,
"recipientType": "CUSTOM",
"status": "SENT",
"successCount": 1,
"failCount": 0
}
],
"page": 0,
"perPage": 20,
"total": 1,
"totalPages": 1,
"hasNext": false
}

실패, 재시도, 관측성

  • 두 발송 경로 모두 sender 누락과 빈 수신자 집합을 거부합니다.
  • sendToClass(...)는 존재하지 않는 class와 등록 학생이 없는 class도 거부합니다.
  • 발송 endpoint는 선택적 idempotency key를 지원하지만, 템플릿 생성과 템플릿 수정은 idempotency layer를 사용하지 않습니다.
  • 아직 outbound SMS provider adapter가 없기 때문에, provider 측 실패, retry 정책, delivery callback은 현재 런타임에 존재하지 않습니다.
  • sms_messagessms_templates 모두 tenant table이므로, 이력 조회는 RLS 아래에서 tenant 범위를 유지합니다.

검증

cd lumie-backend
./gradlew :modules:notification:test
./gradlew :modules:notification:test --tests '*Sms*'

예상 성공 신호:

  • Gradle이 BUILD SUCCESSFUL로 종료되고, notification 테스트가 여전히 발송 및 이력 endpoint를 다룹니다.
  • PageResponse가 여전히 SMS 이력을 items, page, perPage, total, totalPages, hasNext로 직렬화하고, SmsCommandService가 mock 발송을 즉시 SENT로 표시합니다.

관련 페이지