Refactor: 문서 목록 CQRS Read Model 전환 및 Domain Event Outbox 추가#203
Merged
Conversation
…스케이스 메서드화로 호출부에서의 의미 들어나게 변경 - flyway 마이그레이션 sql문 생성(originType 삭제)
- Factory -> JobEnqueuer - DeleteService -> DeleteExecutor - MongoDeleteOutboxCreateService를 제거하고 JobEnqueuer에서 saveAndFlush를 직접 호출 - 변경된 이름과 중복 충돌 처리 방식에 맞춰 테스트 수정
- 도메인 이벤트 저장용 outbox 엔티티와 마이그레이션을 추가 - outbox relay, lifecycle service, wake-up listener를 추가 - 로컬 dispatcher를 통해 문서 목록 projector로 이벤트를 전달하도록 구성 - 문서 목록 조회용 Mongo read model과 payload를 추가 - 신규 JPA/Mongo repository 패키지를 스캔 설정에 등록
- 문서 목록 projection payload를 이벤트 타입별 record로 분리 - DocListProjector가 이벤트 타입별 payload를 역직렬화하도록 변경 - DocListReadModel에 생성/제목/활동/썸네일/삭제 이벤트별 반영 메서드를 추가 - DocPayloadFactory를 추가해 이벤트 payload 생성 책임을 분리
- 문서 생성 트랜잭션에서 DOC_CREATED outbox 이벤트를 저장하도록 연결 - 문서 제목 변경 시 DOC_TITLE_CHANGED outbox 이벤트를 저장하도록 연결 - 제목 변경 단위 테스트에 outbox publish 검증을 추가
- @EnableAsync 설정을 AsyncConfig로 분리 - outbox wake-up listener가 전용 executor를 사용하도록 변경 - 트랜잭션 커밋 이후 이벤트만 처리하도록 fallbackExecution을 제거
- 브랜치 생성, 병합, 커밋 생성, 저장 수정, 썸네일 확정, 문서 삭제 흐름에서 문서 목록 read model 갱신용 domain event outbox를 발행 연결 - 브랜치가 save를 가진다는 현재 도메인 규칙에 맞춰 커밋 통합 테스트 fixture를 보정
- DocListProjector의 이벤트별 read model 반영과 eventId 기반 idempotency를 단위 테스트로 검증 - 오래된 DOC_DELETED 이벤트가 최신 read model 상태를 덮어쓰지 않도록 lastProjectedEventId 반영을 보정 - DomainEventOutboxRelay의 DONE/retry/FAILED 상태 전이를 통합 테스트로 검증 - relay, dispatcher, projector, Mongo read model까지 이어지는 DOC_CREATED projection 통합 테스트를 추가
- DocService를 DocCommandService로 분리하고 문서 목록/검색/그래프 조회를 DocQueryService로 이동 - 문서 목록/검색 응답을 DocListReadModel 기반으로 매핑하도록 변경 - 기존 SQL 목록 조립용 DocListAssembler를 제거하고 read model mapper를 추가 - command/query 분리에 맞춰 컨트롤러 및 테스트를 정리
- 컨트롤러 테스트의 MockMvc print 출력을 제거해 테스트 로그 노이즈를 줄임 - test/local/stg/prod 환경의 SQL 및 bind parameter 로그 레벨을 명시 - local 환경에서는 필요 시 환경변수로 SQL 로그를 켤 수 있도록 조정
- 문서 도메인 테스트를 api/app 단위와 통합 테스트 패키지로 정리 - DocQueryService 목록 응답에서 thumbnailObjectKey null/blank 처리 검증 추가 - thumbnailObjectKey가 있으면 CDN URL로 변환되는지 검증
- local 프로필에서 Mongo DB를 시작/종료 시 정리하는 cleanup 컴포넌트 추가 - test 프로필에서 테스트 DB 정리를 위한 cleanup 컴포넌트 추가 - 안전한 DB 이름(-local, -test)에 대해서만 drop 하도록 보호 로직 추가 - cleanup 동작을 검증하는 단위 테스트 추가
- 커밋 조회/검증 책임을 CommitReader로 분리 - 커밋 저장/삭제 책임을 CommitWriter로 분리 - CQRS QueryService와 혼동되는 기존 CommitQueryService 명명을 제거
- 브랜치 조회/검증 책임을 BranchReader로 분리 - 브랜치 생성/저장/삭제 책임을 BranchWriter로 분리 - CQRS QueryService와 혼동되는 기존 BranchQueryService 명명을 제거
- 저장 조회/검증 책임을 SaveReader로 분리 - 저장 생성/갱신 책임을 SaveWriter로 분리 - CQRS QueryService와 혼동되는 기존 SaveQueryService 명명을 제거
- 이미지 조회 전용 서비스를 ImageReader로 변경 - 썸네일 영속성 접근을 ThumbnailStore로 정리 - CQRS QueryService와 혼동되는 기존 QueryService 명명을 제거
- 기존 Doc 데이터를 batch 단위로 조회해 DocListReadModel을 초기 생성 - Mongo setOnInsert upsert로 기존 read model을 덮어쓰지 않도록 처리 - backfill profile에서 실행되는 one-off job과 테스트 설정 추가
- local backfill 실행 스크립트를 추가 - backfill profile에서 local initializer와 cleanup이 실행되지 않도록 조정 - backfill 실행 시 domain event relay bean 의존성은 유지하되 scheduler 실행은 지연
- 목록 조회에서 deleted=true read model이 제외되는지 검증 - 제목 검색 조회에서도 삭제된 read model이 제외되는지 검증
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🛰️ Issue Number
🪐 작업 내용
개요
문서 목록 조회 흐름을 기존 SQL 조합 방식에서
DocListReadModel기반 CQRS 조회로 전환했습니다.기존 문서 목록 API는
docs조회 이후 branch/save/thumbnail 상태를 매번 조합해서 응답을 만들었습니다. 이번 변경에서는 문서 목록 화면에 필요한 값을 MongoDB read model로 유지하고, write model 변경은DomainEventOutbox를 통해 projector로 전달하도록 구성했습니다.이번 PR의 핵심은 다음입니다.
DocListReadModel추가DomainEventOutbox기반 projection 흐름 추가DocCommandService/DocQueryService분리QueryService명명 혼동 정리주요 변경 사항
1. Domain Event Outbox 추가
문서 목록 read model 갱신을 위해 domain event outbox 구조를 추가했습니다.
추가된 주요 구성은 다음과 같습니다.
DomainEventOutboxDomainEventOutboxRepositoryDomainEventOutboxPublisherDomainEventOutboxRelayDomainEventOutboxLifecycleServiceDomainEventOutboxWakeUpListenerDomainEventDispatcherLocalDomainEventDispatcherDomainEventMessage쓰기 트랜잭션 안에서는 outbox row만 저장하고, commit 이후 wake-up listener가 relay를 깨우도록 했습니다. wake-up은 지연을 줄이기 위한 신호이고, 실제 복구 가능성은 outbox row와 scheduler fallback이 담당합니다.
relay는 다음 흐름으로 동작합니다.
OPENoutbox 조회PROCESSING상태로 claimDomainEventMessage로 변환DONEFAILED2. 문서 목록 read model 추가
MongoDB에 문서 목록 조회 전용 read model을 추가했습니다.
주요 필드:
id: docIduserIdtitlecreatedAtupdatedAtrecentSaveIdthumbnailObjectKeythumbnailStatusdeletedlastProjectedEventIdlastProjectedEventId를 통해 이미 처리한 이벤트 이하의 projection은 무시하도록 했습니다.3. 이벤트별 payload 분리
초기 단일 payload 구조 대신 이벤트별 payload record로 분리했습니다.
DocCreatedPayloadDocTitleChangedPayloadDocActivityChangedPayloadDocThumbnailChangedPayloadDocDeletedPayload각 이벤트가 필요한 값만 갖도록 분리했고, payload 생성 책임은
DocPayloadFactory로 모았습니다.4. 문서 목록 갱신 이벤트 발행 연결
문서 목록 read model에 영향을 주는 쓰기 유스케이스에서 domain event outbox를 발행하도록 연결했습니다.
연결된 이벤트:
DOC_CREATEDDOC_TITLE_CHANGEDDOC_DELETEDDOC_ACTIVITY_CHANGEDDOC_ACTIVITY_CHANGEDDOC_ACTIVITY_CHANGEDDOC_ACTIVITY_CHANGEDDOC_THUMBNAIL_CHANGED현재 문서 목록 projection 이벤트는 모두
AggregateType.DOC을 사용하고,aggregateId에는docId를 저장합니다.5. 문서 목록 조회 API를 read model 기반으로 전환
기존
DocService중심 조회 구조를 분리했습니다.DocCommandService: 문서 생성, 제목 변경, 삭제 등 write model 변경 담당DocQueryService: 문서 목록/사이드바/검색/그래프 조회 담당문서 목록, 사이드바, 검색 조회는
DocListReadModelRepository기반으로 전환했습니다.조회 흐름:
getSimplePageDocListReadModelRepository.findByUserIdAndDeletedFalseDocListReadModelMapper.toListSimpleResponsegetPageDocListReadModelRepository.findByUserIdAndDeletedFalseDocListReadModelMapper.toListResponsesearchListDocListReadModelRepository.findByUserIdAndDeletedFalseAndTitleContainingIgnoreCaseDocListReadModelMapper.toListResponse그래프 조회는 문서 목록 read model 대상이 아니므로 기존
DocReader,BranchReader,CommitReader,EdgeService조합을 유지했습니다.6. 기존 QueryService 명명 정리
일부 도메인에서
QueryService라는 이름을 사용하고 있었지만, 실제 역할은 CQRS read model query service가 아니라 repository 접근 helper에 가까웠습니다.CQRS 문맥에서의
DocQueryService와 혼동을 줄이기 위해 다음과 같이 정리했습니다.CommitQueryService→CommitReader/CommitWriterBranchQueryService→BranchReader/BranchWriterSaveQueryService→SaveReader/SaveWriterThumbnailQueryService→ThumbnailStoreImageQueryService→ImageReader7. Backfill job 추가
기존 문서 데이터를
doc_list_read_models로 초기화하기 위한 backfill job을 추가했습니다.추가된 구성:
DocListReadModelBackfillServiceDocListReadModelBackfillJobapplication-backfill.ymlinfra/scripts/backfill-doc-list-local.shbackfill은 batch 단위로 MySQL
docs를 읽고, 최신 save id와 썸네일 상태를 조합해 Mongo read model을 생성합니다.저장은 Mongo
$setOnInsert기반 upsert를 사용했습니다.이유:
따라서 이미 read model이 존재하면 어떤 필드도 수정하지 않고, 없는 경우에만 insert합니다.
8. Local/Test Mongo cleanup 추가
local/test 환경에서 MongoDB 데이터 정리가 가능하도록 cleanup bean을 추가했습니다.
단, backfill profile에서는 기존 Mongo 데이터를 보존해야 하므로 다음 initializer들은 backfill profile에서 제외했습니다.
LocalMongoCleanupPerfDataInitializerTestUserInitializer운영 및 배포 메모
Backfill 실행 방식
Backfill은 API 서버 요청으로 실행하지 않고, 별도 one-off job 프로세스로 실행하는 것을 전제로 합니다.
운영 예시:
테스트
Projection
DocListProjectorUnitTest
DocListProjectorIntegrationTest
Relay
Query
Backfill
리스크 및 후속 작업
리스크
후속 작업
📚 Reference
✅ Check List