ADR-001: 두 층 API
v2 공개 surface 로서의 hwpapi.low + App.doc.*
상태
Accepted — 2026-04-18
v2 재설계 계획(.omc/plans/hwpapi_v2_redesign.md)의 Phase 1–5 에 걸쳐 구현되었습니다.
맥락
hwpapi v1.0 은 __init__.py 의 네 개 wildcard import 를 통해 최상위에 144 개 이상의 심볼을 노출했습니다.
from .core import *
from .actions import *
from .functions import *
from .parametersets import *App 클래스 하나만 해도 82 개의 공개 멤버를 가졌으며, 다음을 뒤섞고 있었습니다.
- 라이프사이클 (open/save/close/quit)
- 문서 상태 (text, cursor, selection)
- 문서별 collection (fields, bookmarks, images, styles)
- 요소 상태 (charshape, parashape)
- 서식 scope (charshape_scope, parashape_scope)
- 단위 변환 (mm_to_hwpunit 등)
- low-level COM 유틸리티 (actions, parameters, register_security_module)
결과적으로 단일한 멘탈 모델이 없었고, 모든 작업에 두세 가지 방법이 있었으며, core/app.py 는 3,290 줄에 달했고, 사용자가 실제로 탐색하기 어려운 import surface 가 만들어졌습니다.
결정
공개 API 를 명시적이고 동등한 두 층으로 재구성합니다.
- High layer —
hwpapi.App→app.doc.*. 의견이 반영되어 있고 작으며 collection 중심입니다. 멤버 ≤15 인App이 라이프사이클을 소유하고,Document가 문서별 상태를 소유하며, collection 은 하나의 프로토콜을 따르고, 요소는 값 객체입니다. - Low layer —
hwpapi.low.actions,hwpapi.low.parametersets,hwpapi.low.engine. 원시 액션 래퍼와 ParameterSet 클래스입니다. 공식적으로 지원되며, deprecated 가 아닙니다. high layer 는 low layer 의 용어로 표현되며 — 결코 그것과 독립적이지 않습니다.
__init__.py 는 정확히 세 개의 이름만 export 합니다: App, Document, __version__.
결정 동인
- 일관성 — 모든 사용자 경로는 같은 몇 개의 진입점을 거치며, collection 은 한 가지 형태, context scope 도 한 가지 형태입니다.
- 학습 가능성 —
app.,app.doc.,app.doc.fields[...]의 IDE 탭 자동완성으로 일상 작업의 95% 가 문서를 열지 않고 해결되어야 합니다. - 유지보수성 — 작은 공개 surface 는 모든 변경의 비용을 줄입니다. 명확한 layer 경계는 기능 표류(feature drift)를 막습니다.
검토한 대안들
Option A — 계층적 도메인 모델 (Office Automation 스타일)
app.doc.paragraphs[i].runs[j].charshape.bold = True
장점: Word/Excel 자동화 사용자에게 친숙하며, collection 일관성이 최대화됩니다.
단점: HWP 는 내부적으로 근본적으로 위치 기반이라 — 일부 drill-down 경로(runs)는 가상화가 필요합니다. 이는 high layer 의 형태가 되었지만 전체 API 는 아닙니다.
Option B — 평면 동사형 API
app.fill_field(), app.set_charshape(), app.goto_bookmark()
장점: 하나의 namespace, 최소한의 계층.
단점: 새로운 이름으로 v1 의 App-as-god-object 문제를 재현합니다. 슬림 facade 목표를 무력화합니다.
기각.
Option C — 두 층 (low + high) ← 선택됨
Django ORM 패턴: Model.objects 가 의견이 반영된 공개 surface 이고 django.db.connection 이 탈출구이며, 두 가지가 모두 1급 API 로 공존합니다.
v2 의 형태로 채택되었습니다. Option A 의 계층적 도메인 모델은 App.doc.* 안에 살고, Option D 의 술어 필터링은 Collection.filter(predicate) 를 통해 부분적으로 채택되었습니다.
Option D — 쿼리 중심 (pandas 스타일)
doc.paragraphs.where(style="제목").set(bold=True)
장점: 강력한 일괄 연산.
단점: HWP 는 stream 기반입니다 — where 는 eager iteration 으로 귀결되며, 큰 문서에서 성능 절벽이 발생합니다.
부분 채택 — collection.filter(predicate) 가 쿼리 모델의 부분집합입니다.
Option E — deprecation shim 유지
장점: v1 사용자에게 끊김 없는 마이그레이션.
단점: 유지보수 surface 가 두 배가 되고(공개 API 두 개, 문서 경로 두 개), 슬림 facade 목표를 무력화하며, 타입 수준에서 “App 수준 vs. Document 수준” 구분이 불가능해집니다. 82 멤버 footprint 를 영원히 보존하게 됩니다.
기각 — 사용자 동의에 따라. v2 는 깔끔한 단절이며, v1 은 v1.x 브랜치에 동결됩니다.
결과
긍정적
- 문서 IA 가 코드 구조와 1:1 로 일치 — sidebar 항목이 import tree 노드와 대응됩니다.
- 새 기능은 추가되기 전에 “어느 layer 인가?” 에 답해야 하므로, v1 식 표류를 방지합니다.
hwpapi.low는 추출 가능합니다 — 너무 커지면 high-level API 를 깨뜨리지 않고 별도 패키지로 출시할 수 있습니다.dir(app)가 한 화면에 들어갑니다.
부정적
- v1 스크립트가 깨집니다. 완화책: 마이그레이션 가이드 가 제거된 모든 멤버에 대해 v2 후속 또는 탈출구 포인터를 한 행씩 제공합니다.
- layer 정책에는 경계가 필요합니다. 완화책:
docs/design/architecture.qmd가 “이것이 facade 에 속하는가?” 시험을 정의하며, 공개 심볼을 추가하는 모든 PR 은 layer 선택을 정당화해야 합니다.
중립
- 이제 문서는 단일 source 입니다(
docs/Quarto). 이전의 nbs/ + examples/ + docs/*.md 분리는 통합되었습니다.
후속 작업
- v2.1: Option D 의 일부 — 일괄 편집을 위한
app.doc.paragraphs.where(style=...).update(...)를 Collection 확장으로 검토. - v2.x:
hwpapi.low.parametersets가 계속 커지면 별도 출시 sub-package 로 분리.
참고
.omc/plans/hwpapi_v2_redesign.md— 이 ADR 이 추출된 계획서docs/design/app-member-audit.md— v1App의 82 개 멤버 분류docs/design/baseline-v1.0.md— 회귀 검사용 v1 성능 베이스라인