Skip to content

ARD-0000 — 아키텍처 진단 및 초기 설계 방향

날짜: 2026-03-18
상태: 완료 (전체 반영)


배경

프로젝트를 NPM 라이브러리로 공개하기 전, 현재 구조의 문제점을 체계적으로 진단하고 개선 방향을 수립했습니다. 이 문서는 그 진단 결과와 결정 사항을 기록합니다.

진단된 문제

1. 디렉토리 파편화

핵심 도메인 모델(DomainState, DomainVO, DomainPipeline)이 src/ 외부인 루트의 model/ 디렉토리에 위치해 있었고, 플러그인도 별도의 plugin/ 디렉토리에 분리되어 있었습니다.

이는 번들러의 Entry Point 추적을 복잡하게 만들고 Tree-shaking 효율을 떨어뜨립니다. tsconfig.jsoninclude 배열이 src/**/*, model/**/*, plugin/**/* 세 곳을 따로 가리키는 것이 파편화의 증거입니다.

결정: 모든 소스를 src/ 하위로 통합. model/src/domain/, plugin/src/plugins/, src/handler/src/network/. 진입점 파일명을 컨벤션에 맞게 rest-domain-state-manager.jsindex.js 로 변경.

2. HTTP 메서드 분기 로직의 결함

당시 save() 의 분기 로직은 changeLog.length > 0 이면 PATCH, 아니면 PUT 이었습니다. 이는 두 가지 의미론적 오류를 내포합니다.

첫째, changeLog 는 RFC 6902 감사 이력이지 "전체 교체 필요 여부"의 판단 기준이 아닙니다. 둘째, 필드의 90%가 변경된 경우에도 항상 PATCH를 보내는 것은 비효율적입니다.

결정: _dirtyFields: Set<string> 를 새로 도입. 변경된 최상위 키 비율(dirtyRatio = dirtyFields.size / totalFields)을 기준으로 DIRTY_THRESHOLD = 0.7 이상이면 PUT, 미만이면 PATCH로 분기. 자세한 내용은 HTTP 자동 라우팅 참고.

3. 순환 참조 해소 방식

DomainStateDomainPipeline 의 상호 참조를 해소하기 위해 globalThis.__DSM_DomainPipeline 전역 변수를 사용하고 있었습니다. 이는 설계가 꼬였다는 것을 스스로 증명하는 코드입니다.

결정: Constructor Injection 패턴 채택. index.js 진입점에서 DomainState.PipelineConstructor = DomainPipeline 을 한 번 할당. 두 모듈이 서로를 직접 import하지 않으며, 전역 오염 없음.

4. V8 최적화 부재

DomainVO 없이 서버 응답을 직접 빈 객체에 동적 할당하면 V8 Hidden Class가 계속 전이되어 Inline Caching이 무력화됩니다. 또한 Proxy 트랩 내부에서 원시 접근(target[prop])을 사용하여 상속 구조가 복잡한 객체에서 Context Loss 버그 가능성이 있었습니다.

결정: DomainVO.fields 로 프로퍼티 구조 사전 선언. Proxy 트랩 내부 전체를 Reflect API로 교체. WeakMap 기반 Lazy Proxying 도입.

영향 범위

이 결정들은 Milestone 1(Dirty Checking), Milestone 2(디렉토리 재구조화), 그리고 이후 전체 아키텍처에 지속적인 영향을 미쳤습니다.

Released under the ISC License.