--- id: wiki-2026-0508-단일-진실-공급원-single-source-of-truth title: 단일 진실 공급원(Single Source of Truth) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [SSOT, Single Source of Truth, 진실의 단일 공급원] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [architecture, state-management, ssot, frontend] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: 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 ```typescript // ❌ duplication — local state mirrors server const [user, setUser] = useState(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) ```typescript 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 ; } ``` ### Derived state (no duplicate store) ```typescript // ❌ 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 ```typescript const { register, watch, handleSubmit } = useForm({ defaultValues: { email: '', plan: 'free' }, }); const plan = watch('plan'); // derived from form SSOT return (
{plan === 'pro' && }
); ``` ### Zustand store SSOT ```typescript import { create } from 'zustand'; type CartStore = { items: CartItem[]; add: (item: CartItem) => void; remove: (id: string) => void; }; export const useCart = create((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) ```typescript 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 - 부모: [[State Management]] - 변형: [[Event Sourcing]] · [[CQRS]] - 응용: [[React Server Components — 경계 의식]] · [[CRDT]] - Adjacent: [[단일 진실 공급원(Single Source of Truth) 구축]] ## 🤖 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 |