이벤트 소싱

Event sourcing

최종 값 대신 상태를 만든 이벤트 순서를 보관합니다.

···
html
<div class="scene"><div class="title">REPLAY THE LOG</div><div class="ledger" id="ledger"><span>+100</span><span>−30</span><span>+20</span><span>−10</span></div><div class="row result"><div class="tile">BALANCE <strong id="balance">0</strong></div><div class="muted" id="eventLabel">start</div></div></div>
css
.scene{width:min(94vw,760px);height:min(86vh,320px);padding:clamp(10px,2.5vmin,18px);border:1px solid var(--line);border-radius:14px;background:var(--surface);color:var(--fg);font:500 clamp(11px,1.3vw,16px)/1.25 "Pretendard Variable",Pretendard,-apple-system,sans-serif;display:flex;flex-direction:column;gap:clamp(7px,1.6vmin,12px);overflow:hidden}.scene *{box-sizing:border-box}.title{font-weight:750;color:var(--accent)}.muted{color:var(--muted)}.mono{font-family:ui-monospace,monospace}.row{display:flex;align-items:center;justify-content:center;gap:clamp(5px,1.4vmin,12px);flex:1;min-height:0}.tile{border:1px solid var(--line);border-radius:8px;background:var(--bg);padding:clamp(6px,1.6vmin,12px);text-align:center;min-width:0}.on{border-color:var(--accent)!important;background:color-mix(in srgb,var(--accent) 12%,var(--surface))!important}.pulse{animation:pulse 1.6s infinite alternate}@keyframes pulse{to{border-color:var(--accent);transform:translateY(-3px)}}.ledger{display:flex;justify-content:space-between;gap:5px}.ledger span{flex:1;text-align:center;border:1px solid var(--line);border-radius:5px;padding:8px 2px;font-family:ui-monospace,monospace}.ledger span.on{color:var(--accent)}.result .tile{min-width:45%}.result strong{font:700 1.7em ui-monospace,monospace;color:var(--accent)}
js
const deltas=[100,-30,20,-10],cells=[...document.querySelectorAll('#ledger span')];let step=0;function tick(){let balance=0;cells.forEach((cell,i)=>{const active=i<=step;cell.classList.toggle('on',active);if(active)balance+=deltas[i]});document.getElementById('balance').textContent=balance;document.getElementById('eventLabel').textContent='event '+(step+1)+' / 4';step=(step+1)%4}tick();setInterval(tick,850)

이벤트 소싱은 현재 상태만 덮어쓰는 대신 발생한 사실을 순서대로 저장합니다. 읽을 때 이벤트를 재생하거나 스냅샷 이후 부분만 적용해 현재 값을 복원합니다.

데모는 입금과 출금 로그를 한 칸씩 재생하며 잔액을 다시 계산합니다. 이벤트 스키마의 장기 호환성과 개인정보 삭제 요구를 설계해야 합니다.

언제 쓰나

변경 이력 자체가 중요하고 과거 상태를 재구성해야 할 때 사용합니다.

페이지로 열기 ↗