|| WORXPHERESpring Event 기반 2-Tier 이력 관리 아키텍처 설계기들어가며
안녕하세요, 저희는 잡코리아에서 Display Catalog 시스템 백엔드 개발을 담당하고 있습니다.
지난 글에서는 Section API의 설계와 버전 퍼블리시 아키텍처에 대해 이야기했는데요, 이번 글에서는 SD-UI 시스템의 변경 이력 관리에 대해 이야기해보려 합니다.
SD-UI는 “배포 없이 화면을 바꿀 수 있다”는 강력한 장점이 있지만, 그만큼 “누가 언제 뭘 바꿨는지” 추적하는 게 매우 중요합니다. 잘못된 변경이 바로 앱 화면에 반영될 수 있으니까요.
이 글에서는 이력 관리를 어떻게 설계했고, 어떤 고민을 거쳤는지 공유하겠습니다.
배경: 왜 이력 관리가 필요했는가
Admin 시스템은 운영자가 화면에서 데이터를 수시로 편집하고, 그 결과를 캐시에 배포하여 사용자에게 노출하는 구조입니다.
운영을 하다 보면 자연스럽게 이런 질문들이 생깁니다.
“오늘 오전에 누가 이 섹션을 수정했지?”
“캐시 배포 후 화면이 이상한데, 이전 버전으로 되돌릴 수 있어?”
“마지막 배포 이후로 어떤 섹션들이 변경되었지?”
이 질문들을 해결하기 위해 변경 이력(History)과 캐시 배포 이력(CacheSyncHistory), 두 가지 계층의 이력 관리 시스템을 설계했습니다.
2-Tier History 아키텍처전체 구조두 계층의 역할
History (변경 이력) vs CacheSyncHistory (배포 이력)
목적
History (변경 이력): 개별 변경 건에 대한 감사 추적
CacheSyncHistory (배포 이력): 캐시에 실제 반영된 버전 스냅샷
기록 단위
History (변경 이력): 건별 (CREATE / UPDATE / DELETE)
CacheSyncHistory (배포 이력): 버전별 (e.g. v20251111080312)
저장 내용
History (변경 이력): 변경된 엔티티의 JSON 스냅샷 + 변경 필드(diffData)
CacheSyncHistory (배포 이력): 해당 버전의 전체 JSON 스냅샷
주요 활용
History (변경 이력): “누가, 언제, 무엇을 변경했는가”
CacheSyncHistory (배포 이력): 버전 비교, 현재 적용 버전 확인
API
History (변경 이력): 생성(POST) + 조회(GET)
CacheSyncHistory (배포 이력): 조회(GET)
History는 운영자의 개별 행위를 추적하고, CacheSyncHistory는 서비스에 실제로 반영된 상태를 관리합니다. 이 두 계층이 만나는 지점이 “마지막 캐시 동기화 이후 변경된 이력 조회” 기능입니다.
Spring Event 기반 비동기 이력 저장
이력 저장에서 가장 중요한 원칙은 “이력 저장 실패가 비즈니스 로직에 영향을 주어서는 안 된다” 입니다.
흐름AppService — 이벤트 발행
@Transactional
public void create(HistoryCreateReq request) {
// 이벤트 발행 - 실제 저장은 트랜잭션 커밋 후 EventListener에서 처리
eventPublisher.publishEvent(toEvent(request));
}
컨트롤러는 @ResponseStatus(HttpStatus.ACCEPTED)로 202 응답을 반환합니다. "요청은 접수했고, 이력 저장은 비동기로 처리하겠다"는 의미입니다.
EventListener — 트랜잭션 커밋 후 저장
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleHistoryCreateEvent(HistoryCreateEvent event) {
try {
historyService.create(toModel(event));
} catch (Exception e) {