본문으로 건너뛰기

출석 서비스

이 페이지는 lumie-backend/modules/attendance를 다룹니다.

책임

attendance 모듈은 다음을 소유합니다.

  • 날짜별 또는 class별 attendance session
  • 학생별 attendance record
  • 코드 기반 학생 체크인
  • class, student, dashboard 출결 통계
  • 출결 이력 CSV export

class 모듈과 student 모듈과 달리, attendance는 현재 internal libs/internal-api 계약을 노출하지 않습니다. 다른 모듈은 attendance 측 service API가 아니라 class 조회와 공유 tenant 범위 table을 통해 통합합니다.

소스 경로

Path역할
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/adapter/in/web공개 HTTP controller
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/application/servicesession, record, statistics, export service
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/entityAttendanceSession, AttendanceRecord, StudentReadModel
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/repositorysession, record, student read repository
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sqlbaseline attendance_sessions, attendance_records table
lumie-backend/app/src/main/resources/db/migration/public/V20__rename_qna_is_it_answered.sqlattendance table에 optimistic-lock version column 추가

공개 인터페이스

SurfaceEntrypoints
Session CRUDPOST /v1/attendance/sessions, GET /v1/attendance/sessions, GET /v1/attendance/sessions/{id}, POST /v1/attendance/sessions/{id}/regenerate-code, DELETE /v1/attendance/sessions/{id}
Session recordsGET /v1/attendance/sessions/{sessionId}/records, PATCH /v1/attendance/sessions/{sessionId}/records/{recordId}, POST /v1/attendance/sessions/{sessionId}/records/bulk-update
Class-oriented helpersPOST /v1/attendance/classes/{classId}/sessions/ensure, GET /v1/attendance/classes/{classId}/statistics, GET /v1/attendance/classes/{classId}/students
Student self check-inPOST /v1/attendance/check-in
Student historyGET /v1/attendance/students/{studentId}/records, GET /v1/attendance/students/{studentId}/statistics
Dashboard and exportGET /v1/attendance/statistics/dashboard, GET /v1/attendance/records/export.csv

집계와 읽기 모델

TypeTable참고
AttendanceSessionattendance_sessionssession date, 선택적 class 연결, attendance code, 지각 기준, record collection 저장
AttendanceRecordattendance_records(session_id, student_id) unique
StudentReadModelstudents변경 불가능한 read-only student projection

StudentReadModel은 의도적인 cross-module read model입니다. attendance 모듈은 자체 student cache를 두는 대신, 불변에 가까운 이름과 user 연결을 위해 students table을 직접 읽습니다.

런타임 흐름

반 세션 보장 흐름

학생 체크인 흐름

핵심 동작

  • ensureSessionForClass(...)(classId, KST 기준 오늘)에 대해 idempotent하며, 이미 존재하면 기존 session을 반환합니다.
  • class용 session 생성은 ClassService.getEnrolledStudentIds(...)를 이용해 현재 등록 학생 수만큼 record를 미리 만듭니다.
  • class가 없는 단독 session 생성은 StudentReadRepository.findByIsActiveTrue()의 모든 활성 학생을 사용합니다.
  • check-in은 payload의 student ID가 아니라 인증된 userId로 학생을 해석합니다.
  • dashboard 통계는 미결정 row가 출석률을 희석하지 않도록 PENDING record를 제외합니다.
  • CSV export는 session을 페이지 단위로 읽고, 각 페이지별로 record와 student 이름을 메모리에서 join합니다.

대표 계약 예시

이 예시는 ClassAttendanceController, StudentCheckInController, AttendanceSessionResponse, CheckInResponse, AttendanceSessionCommandService, AttendanceRecordCommandService, 관련 controller/service test와 일치합니다.

오늘의 반 세션 보장

POST /v1/attendance/classes/9/sessions/ensure

{
"id": 77,
"name": "수학반 A",
"sessionDate": "2026-06-14",
"classId": 9,
"className": "수학반 A",
"attendanceCode": "654321",
"codeExpiresAt": null,
"lateThresholdMinutes": 10,
"createdAt": "2026-06-14T00:00:00Z",
"updatedAt": "2026-06-14T00:00:00Z",
"presentCount": 0,
"absentCount": 0,
"lateCount": 0,
"excusedCount": 0,
"totalStudents": 0
}

놓치기 쉬운 세부사항 두 가지:

  • codeExpiresAt은 응답 계약에는 있지만, AttendanceSession.create(...)regenerateCode()가 값을 넣지 않기 때문에 null로 남습니다.
  • ensureSessionForClass(...)fromWithoutRecords(...)를 반환하므로, enrolled student용 record가 생성되었어도 응답 카운트는 0으로 보입니다.

학생 셀프 체크인

POST /v1/attendance/check-in

{
"code": "654321"
}
{
"message": "지각으로 처리되었습니다.",
"status": "LATE"
}

CheckInRequest에는 6자리 code만 들어 있습니다. service는 인증된 user context에서 student를 해석하고, 만료되었거나 알 수 없는 code는 CODE_EXPIRED 또는 INVALID_CODE로 거부합니다.

의존성과 경계

Dependency존재 이유
ClassServiceclass-bound session의 class 이름과 등록 student ID 해석
students read modelcheck-in과 CSV export에 필요한 student 이름과 인증 user 연결 해석

attendance 모듈은 class_id, class_name, student_id를 저장하지만, class와 student 도메인의 source of truth는 여전히 해당 모듈들입니다.

실패 모드

  • class session 생성 또는 ensure는 class 모듈이 대상 class를 해석하지 못하면 CLASS_NOT_FOUND로 실패합니다.
  • check-in은 STUDENT_NOT_FOUND, INVALID_CODE, CODE_EXPIRED, RECORD_NOT_FOUND로 실패합니다.
  • session 및 record 수정은 SESSION_NOT_FOUND 또는 RECORD_NOT_FOUND로 실패합니다.
  • record uniqueness는 table 수준의 (session_id, student_id)로 강제됩니다.

알려진 계약 드리프트

AttendanceSessioncodeExpiresAt을 노출하고, AttendanceRecordCommandService.checkInByCode(...)는 만료 code를 거부하며, AttendanceSessionResponse에도 그 field가 포함됩니다. 하지만 현재 create와 regenerate 경로는 codeExpiresAt을 할당하지 않으므로, 외부 프로세스가 column을 쓰지 않는 한 새 code는 만료되지 않습니다. 이 문서는 의도된 정책이 아니라 현재 코드 그대로를 반영합니다.

관측성

  • session 생성, ensure, 재생성, check-in, bulk update, export는 모두 application service를 통해 로그됩니다.
  • 이 모듈에는 queue, retry loop, background sweeper가 없습니다.

검증

./gradlew :modules:attendance:test
./gradlew :app:test --tests '*Attendance*'
cd lumie-document/docusaurus && npm run build

예상 성공 신호:

  • Gradle이 BUILD SUCCESSFUL로 끝납니다.
  • AttendanceSessionCommandServiceTest, AttendanceRecordCommandServiceTest, StudentCheckInControllerTest가 실패 없이 통과합니다.
  • Docusaurus가 backend/attendance-svc에 대해 MDX 또는 broken-link 오류 없이 완료됩니다.

관련 페이지