Search

앱 배포 없이 화면을 바꾸다: Server-Driven UI 도입기(3)

URL
생성 일시
2026/04/23 04:06
최종 편집 일시
2026/04/23 04:06
태그
잡코리아
파일과 미디어
|| 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) {