프로젝트 설계 철학1
2025.06.11
사용자 중심 다중작업 환경(UI concurrency model)
다중 창(Window) 기반 UI 설계
창 간 독립적인 상태, 비동기 흐름, 이벤트 처리
창의 위치, 크기, 포커스 등 인터랙션 요소 관리
창은 부모-자식 관계를 갖고, 창 위에 창을 띄우는 구조
이는 "단일 인터페이스-단일 작업"이라는 전통적인 웹 UI 철학을 거부하는 구조다. 대신 **사용자의 작업 흐름을 병렬화**하는 데 집중한다. 즉:
콘텐츠를 만들면서 시리즈를 열고, 시리즈에서 다시 포스트를 열고, 동시에 미리보기도 확인할 수 있게 한다
창을 닫지 않고 작업을 유보하거나 전환할 수 있도록 구성한다
이는 데스크탑 애플리케이션, IDE, 또는 운영체제 창 관리자(Window Manager)의 패턴을 차용한 것이다
이 설계 철학은 웹에서도 복잡한 워크플로우를 가진 사용자 경험을 실현할 수 있다는 방향성을 보여주며, UI 복잡도를 수용 가능한 비용으로 설계하여 생산성을 중시하는 방향이다.
데이터 정합성 중심의 클라이언트-서버 역할 분리
서버 fetch와 클라이언트 fetch가 명시적으로 분리되어 있음
데이터 fetch는 서버 구성 요소(App Router)에서 수행하거나, 클라이언트 비동기 요청은 useAsyncTaskManager를 통해 큐에 넣어 실행
서버 응답은 `Response<T>` 타입을 통해 상태 기반 분기 (`ok`, `notfound`, `invalid` 등)
캐시와 상태는 명확히 구분되며, API 요청 결과와 전역 상태는 독립적으로 존재
이 설계는 **클라이언트는 비주얼 및 상호작용 담당**, **서버는 상태와 데이터 일관성 유지 담당**이라는 전통적인 역할 분리를 철저히 적용하고 있다. 이는 다음 원칙을 따른다:
클라이언트는 표현 상태(presentational state)와 사용자 이벤트 처리만 담당
데이터 소스는 React Query 또는 TaskManager를 통해 관리되며, 요청 결과는 별도로 처리
서버에서 데이터는 트랜잭션과 검증을 거쳐 무결성 보장된 상태로 클라이언트에 전달
즉, 이 구조는 "비즈니스 로직은 서버, 뷰 로직은 클라이언트"라는 원칙을 견지하며, 데이터 흐름의 안정성과 예측 가능성을 최우선시하는 철학이 담겨 있다.
도메인 중심 구조(domain-oriented modularization)
fetch, models, validation, UI 모두 도메인(Post, Folder, Series, About 등) 단위로 정리되어 있음
fetch/postInfo/createPost.ts, models/post_info/findByPostId.ts, validation/post.ts 등 명확한 연계성
UI 창 구성도 도메인별로 1:1 매핑되며, 각 도메인의 비즈니스 로직은 해당 창 내부에서 완결됨
이는 기능 중심 구조(functional separation)가 아닌, 도메인 중심 구조(domain-centric modularity)를 따른다. 즉, MVC나 Layered Architecture와는 달리 다음 철학을 따른다:
"기능"이 아니라 "업무 단위(도메인)"에 따라 시스템을 나눈다
도메인 단위로 요청, 상태, UI, 유틸리티까지 모두 함께 묶는다 (high cohesion)
하나의 도메인 변경 시, 관련 코드가 한 디렉토리에 모여 있어 관리가 용이하다
이는 DDD(Domain-Driven Design)의 전략적 모듈화 개념을 반영한 설계 방식이며, 유지보수성과 기능 확장에 강한 구조다.
상태-비동기 흐름 분리(state/effect decoupling)
상태 관리에 Zustand를 사용하되, 서버 fetch 및 캐시 업데이트는 React Query로 분리
직접 store를 갱신하는 로직과 비동기 요청 결과로 갱신하는 흐름이 구분됨
useAsyncTaskManager는 비동기 큐를 관리하고, 요청 후 성공 시 store나 query를 갱신함
이 철학은 상태와 효과(effect)의 분리 를 중시한다. 이는 다음과 같은 고전적 원칙을 따른다:
상태(state)는 변하지 않으며, 단일한 진실(source of truth)을 유지한다
effect는 상태를 바꾸는 하나의 이벤트일 뿐이다
상태를 직접 조작하지 않고, 반드시 effect → 처리 → 상태 갱신의 흐름을 거친다
이것은 Redux-Saga, MobX-state-tree, recoil과 같은 고급 상태 관리 시스템들이 채택하는 구조이며, 비동기 제어의 예측성과 디버깅 가능성을 확보하는 데 중요한 원칙이다.
낮은 결합도와 명시적 의존성 선언(loose coupling, explicit dependency)
의존성 주입 형태는 보이지 않으나, 내부 모듈 간 결합이 느슨함
창 간 데이터는 전역 store나 queryClient를 통해 공유하고, 직접 props 전달은 제한적으로 사용됨
fetch 함수는 전역 유틸로 구성되며, 공통적으로 사용하는 패턴(fetch(url, init))을 유지
이는 결합도를 낮추기 위한 두 가지 전략을 따른다:
모듈 간 직접 참조보다 간접 상태/이벤트 버스를 통해 동기화
기능이 아니라 역할 단위 인터페이스로 구성하여, 확장이나 교체가 용이하도록 설계
이는 특히 다중 창 UI라는 복잡한 상태 트리 안에서도 각 모듈이 서로의 상태를 직접 참조하지 않고, 정의된 인터페이스만 통해 상태를 읽고 쓰도록 강제하는 설계 철학이다. 이는 규모가 커졌을 때 유지보수성을 극대화하는 설계 전략이다.
API 중심 단방향 흐름 구조 (Unidirectional Data Flow via API Contracts)
모든 서버 요청은 fetch 함수를 통해 명시적으로 실행되며, 서버 응답은 Response<T> 타입으로 강제됨
클라이언트는 fetch 결과를 기반으로 상태(store or query)를 업데이트하며, 서버-클라이언트 간 상태를 이중 유지하지 않음
비동기 요청은 단방향 흐름을 따름: 요청 → 응답 → 상태 반영
이 철학은 React의 기본 철학인 단방향 데이터 흐름 을 API 계층까지 확장한 것이다. 이를 통해 다음을 실현한다:
클라이언트 상태와 서버 상태의 충돌을 방지하며, 항상 서버 응답을 기준으로 클라이언트 상태를 보정
fetch 함수는 상태를 바꾸는 수단이 아니라 서버와의 계약(API Contract) 실행으로 인식됨
예외 상황 (timeout, unauthorized, invalid 등)을 명시적 타입 처리함으로써 불확실한 상태 전파 차단
이런 설계는 규모가 커질수록 예외 처리 흐름이 단순화되며, 디버깅 시 요청 → 응답 → 처리 구조를 추적하기 쉬워진다.
UX와 생산성을 동시에 고려한 개발자 인터페이스 철학 (Productive DX meets End-user UX)
컴포넌트 구성은 유연하지만 복잡하지 않으며, 창/모달/폼은 분리되어 있음
비즈니스 도메인마다 반복되는 fetch, 상태 업데이트, 모달 실행 흐름이 동일한 인터페이스로 구성됨
에디터, 미리보기, 시리즈 관리 등은 실사용자 입장에서 자연스럽게 구성됨
이 프로젝트는 개발자의 생산성과 사용자 경험을 모두 고려한 균형형 설계 철학 을 따른다:
내부적으로는 코드 일관성을 통해 개발자가 변경/확장/수정하기 쉽게 구성
외부적으로는 사용자 흐름이 끊기지 않도록 자연스러운 UI 흐름 제공
창 단위로 작업을 병렬화함으로써 사용자가 자신의 작업 문맥(context)을 보존할 수 있게 함
내부 확장 가능성과 외부 인터페이스 분리 (Encapsulation of Core Logic)
models/, fetch/, lib/ 등 핵심 비즈니스 로직은 UI와 명시적으로 분리되어 있음
컴포넌트는 최대한 상태나 직접 fetch를 하지 않고, 전역 상태나 비동기 매니저를 통해 간접 호출
내부에서만 사용하는 유틸(예: buildFolderChildrenMap)은 UI 계층에 노출되지 않음
이 철학은 "핵심 로직은 외부와 분리하되, 외부에서 쉽게 조작할 수 있도록 인터페이스는 노출한다"는 객체지향 캡슐화 원칙에 기반한다:
UI 변경에 의해 내부 로직이 영향을 받지 않도록 함
내부 구조가 변경되더라도 API, 유틸, fetch 계층만 유지하면 외부 호출은 그대로 유지
이는 테스트, 확장, 리팩토링에 모두 유리하며, 실제 프로덕션 환경에서 기능 추가 시 코드 변경의 안전성을 높이는 전략이다
데이터-뷰 계층 간 일관성 유지 전략 (State Derivation over Duplication)
데이터를 복사하거나 로컬로 중첩 저장하지 않고, 전역 상태 또는 서버 응답을 통해 derive
예: getPaginatedPostsQuery → 실제로는 postIds만 store에 있고, post 상세는 queryClient에서 관리
창이 생성될 때 필요한 상태는 store나 query에서 유도(derive)되며, 직접 중복 상태를 만들지 않음
이 철학은 상태 동기화 충돌을 피하기 위한 구조 전략이다:
상태는 유일한 원본(source of truth)만 유지한다
나머지 UI에서 필요한 정보는 그 원본으로부터 유도(derive)하여 생성한다
이를 통해 상태 중복으로 인한 UI 갱신 불일치, 캐시 불일치 문제를 원천적으로 제거한다