Files
2nd/10_Wiki/Topics/Frontend/단일 진실 공급원(Single Source of Truth).md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

5.6 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-단일-진실-공급원-single-source-of-truth 단일 진실 공급원(Single Source of Truth) 10_Wiki/Topics verified self
SSOT
Single Source of Truth
진실의 단일 공급원
none A 0.9 applied
architecture
state-management
ssot
frontend
2026-05-10 pending
language framework
typescript react-19

단일 진실 공급원(Single Source of Truth)

매 한 줄

"매 모든 derived state 의 single canonical origin". 매 1970s database normalization 에서 시작 — 매 modern frontend (Redux, TanStack Query, RSC) 의 핵심 원리. 매 duplication 의 elimination → 매 inconsistency bug 의 소멸.

매 핵심

매 정의

  • 매 한 데이터 의 하나의 authoritative location.
  • 매 다른 모든 view/component 의 derive (read-only projection).
  • 매 update 의 single entry point — 매 race / drift 의 prevention.

매 frontend 적용 layer

  • Server state SSOT: 매 backend DB — 매 client 의 cache (TanStack Query, SWR).
  • URL state SSOT: 매 query params / path — 매 shareable, refreshable.
  • Form state SSOT: 매 RHF / Formik 의 form object — 매 controlled input 의 derive.
  • Global UI state SSOT: 매 Zustand / Jotai store — 매 cross-component shared.

매 응용

  1. TanStack Query — server SSOT mirror.
  2. RSC (React Server Components) — server 의 SSOT 그대로 stream.
  3. URL-driven state (nuqs, TanStack Router) — 매 shareable SSOT.
  4. CRDT (Yjs, Automerge) — 매 distributed SSOT.

💻 패턴

Server SSOT via TanStack Query

// ❌ duplication — local state mirrors server
const [user, setUser] = useState<User | null>(null);
useEffect(() => { fetchUser().then(setUser); }, []);

// ✅ SSOT — server is truth, query is cached projection
const { data: user } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

URL as SSOT (nuqs)

import { useQueryState, parseAsInteger } from 'nuqs';

function ProductList() {
  const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
  const [sort, setSort] = useQueryState('sort');
  // URL ?page=2&sort=price is the truth — refresh-safe, shareable
  return <List page={page} sort={sort} />;
}

Derived state (no duplicate store)

// ❌ duplicating filtered list in state
const [items, setItems] = useState(allItems);
const [filter, setFilter] = useState('');
useEffect(() => { setItems(allItems.filter(i => i.name.includes(filter))); }, [filter]);

// ✅ derive on render — items isn't state
const [filter, setFilter] = useState('');
const visible = useMemo(() => allItems.filter(i => i.name.includes(filter)), [filter]);

Form SSOT via RHF

const { register, watch, handleSubmit } = useForm<FormData>({
  defaultValues: { email: '', plan: 'free' },
});
const plan = watch('plan'); // derived from form SSOT
return (
  <form onSubmit={handleSubmit(submit)}>
    <input {...register('email')} />
    {plan === 'pro' && <input {...register('teamSize')} />}
  </form>
);

Zustand store SSOT

import { create } from 'zustand';

type CartStore = {
  items: CartItem[];
  add: (item: CartItem) => void;
  remove: (id: string) => void;
};

export const useCart = create<CartStore>((set) => ({
  items: [],
  add: (item) => set((s) => ({ items: [...s.items, item] })),
  remove: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
}));

// Total derived — not stored
const useCartTotal = () => useCart((s) => s.items.reduce((a, i) => a + i.price, 0));

CRDT SSOT (collaborative)

import * as Y from 'yjs';
import { WebrtcProvider } from 'y-webrtc';

const ydoc = new Y.Doc();
new WebrtcProvider('room-id', ydoc);
const ytext = ydoc.getText('content');

ytext.observe(() => {
  // single source — all peers converge
  render(ytext.toString());
});

매 결정 기준

데이터 종류 SSOT location
영속 entity (user, order) Backend DB → TanStack Query cache
Shareable view state URL params (nuqs)
Form draft RHF / Formik form object
Cross-component UI Zustand / Jotai
Component-local useState (그 component 자체 가 SSOT)
Collaborative CRDT (Yjs)

기본값: 매 server 의 SSOT, 매 client 의 cache projection.

🔗 Graph

🤖 LLM 활용

언제: 매 state architecture design / data flow audit / bug-prone "two stores out of sync" 발견 시. 언제 X: 매 truly independent UI ephemeral state (hover, focus) — 매 local 이 자체 SSOT.

안티패턴

  • Mirror state: 매 server 의 data 를 useState 로 복제. → 매 stale + 두 source 의 drift.
  • Multiple stores 의 same field: Redux + Context + URL 모두 selectedId 보유. 매 update 의 race.
  • Premature derivation cache: 매 derived value 를 별도 state 로 저장. 매 useMemo 충분.
  • localStorage 의 silent SSOT: 매 sync 없는 cross-tab — 매 BroadcastChannel 또는 storage event 필요.

🧪 검증 / 중복

  • Verified (Redux docs — "Three Principles", Dan Abramov; Martin Fowler — SSOT).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — SSOT FULL writeup