프로젝트 설계 철학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 갱신 불일치, 캐시 불일치 문제를 원천적으로 제거한다


프로젝트 설계 철학1 - sim-log