기록 도메인의 명령 서비스 (출석 기록 생성, 데이팅/스터디 참여 기록 저장)
- 포트: 8083
- 데이터베이스: MySQL 8.0
- Kafka: localhost:9092
- 인증: JWT (X-Owner-Id 헤더)
- CQRS Command Side (쓰기 전용)
- Event-Driven Architecture (Kafka 기반)
- Event Sourcing (상태 변경 시 이벤트 발행)
- Domain-Driven Design
외부 서비스로부터 참여 완료 이벤트를 수신하여 기록 생성 후 Query Service로 재발행
DatingParticipationReceived - Dating Service에서 수신
Kafka Topic: grewmeet.dating.participation.completed
Record 클래스:
public record DatingParticipationReceived(
UUID studyGroupId, // 데이팅 그룹 ID
UUID userId, // 사용자 ID
UUID meetingId, // 미팅 ID
String studyMeetingEventName, // 이벤트명
LocalDateTime when, // 예정 시간
LocalDateTime createdAt // 생성 시간
) {}처리 과정:
- 이벤트 수신
- 중복 체크 (
meetingId+userId) DatingParticipation엔티티 저장 (MySQL)DatingParticipationRecorded이벤트 발행 → Query Service
StudyParticipationReceived - Study Service에서 수신
Kafka Topic: grewmeet.study.participation.completed
Record 클래스:
public record StudyParticipationReceived(
UUID studyGroupId, // 스터디 그룹 ID
UUID userId, // 사용자 ID
UUID meetingId, // 미팅 ID
String studyGroupName, // 스터디 그룹명
String meetingName, // 미팅명
LocalDateTime completedAt // 완료 시간
) {}처리 과정:
- 이벤트 수신
- 중복 체크 (
meetingId+userId) StudyParticipation엔티티 저장 (MySQL)StudyParticipationRecorded이벤트 발행 → Query Service
Query Service로 기록 이벤트 발행
AttendanceRecordCreated
Kafka Topic: grewmeet.recording.attendance
Record 클래스:
public record AttendanceRecordCreated(
Long id, // 출석 기록 ID
String userId, // 사용자 ID
LocalDateTime attendanceTime, // 출석 시간
LocalDateTime createdAt // 기록 생성 시간
) {}발생 시점: POST /api/attendance API 호출 시
DatingParticipationRecorded
Kafka Topic: grewmeet.recording.dating-participation
Record 클래스:
public record DatingParticipationRecorded(
Long id, // 참여 기록 ID
UUID datingGroupId, // 데이팅 그룹 ID
UUID userId, // 사용자 ID
UUID meetingId, // 미팅 ID
String eventName, // 이벤트명
LocalDateTime scheduledAt, // 예정 시간
LocalDateTime meetingCreatedAt, // 미팅 생성 시간
LocalDateTime recordedAt // 기록 생성 시간
) {}발생 시점: Dating Service 이벤트 수신 후 저장 완료 시
StudyParticipationRecorded
Kafka Topic: grewmeet.recording.study-participation
Record 클래스:
public record StudyParticipationRecorded(
Long id, // 참여 기록 ID
UUID studyGroupId, // 스터디 그룹 ID
UUID userId, // 사용자 ID
UUID meetingId, // 미팅 ID
String studyGroupName, // 스터디 그룹명
String meetingName, // 미팅명
LocalDateTime completedAt, // 완료 시간
LocalDateTime recordedAt // 기록 생성 시간
) {}발생 시점: Study Service 이벤트 수신 후 저장 완료 시
- POST
/api/attendance- 출석 기록 생성- Header:
X-Owner-Id(JWT 인증) - Body:
{ "attendanceTime": "2025-01-03T10:00:00" }(optional) - 제약: 같은 날짜에 중복 출석 불가
- Header:
# MySQL 필요 (Docker Compose 사용)
docker-compose up -d mysql
# 서비스 시작
./gradlew bootRundocker-compose up -did: Long (PK)userId: String (사용자 ID)attendanceTime: LocalDateTime (출석 시간)createdAt: LocalDateTime (생성 시간)
비즈니스 규칙:
- 같은 날짜(년월일)에 한 번만 출석 가능
- 출석 기록 = 출석함 (단순 존재 여부만 표현)
id: Long (PK)datingGroupId: UUIDuserId: UUIDmeetingId: UUIDeventName: StringscheduledAt: LocalDateTimemeetingCreatedAt: LocalDateTimerecordedAt: LocalDateTime
id: Long (PK)studyGroupId: UUIDuserId: UUIDmeetingId: UUIDstudyGroupName: StringmeetingName: StringcompletedAt: LocalDateTimerecordedAt: LocalDateTime
- Java 21 & Spring Boot 3.5.4
- Spring Data JPA & MySQL 8.0
- Apache Kafka Event Streaming
- Lombok 코드 간소화
- Gradle Build Tool
- 출석은 수정/삭제 불가 (생성만 가능)
- 날짜 기반 중복 체크 (같은 날 중복 출석 방지)
- 모든 상태 변경 시 이벤트 발행 필수
- 복잡한 조회는 Query Service에서 처리