From 07b50d698798d1f84591685f4de32e816e033fad Mon Sep 17 00:00:00 2001 From: Ali Zarinkolah Date: Wed, 19 Aug 2026 15:01:10 +0330 Subject: [PATCH] docs(claude): record the deep-module principle for future development --- CLAUDE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 6326a2d..a356ea6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,27 @@ MinIO/Qdrant/SQLAlchemy client-construction code. Use ports only for external side effects/persistence — not around pure local functions. (ADR-0015) +### Prefer deep modules over shallow ones + +When a package exposes several small pure functions that a caller must +compose correctly every time (right dispatch, right order, right +thread/async offload), give it one entry point that owns that composition, +and keep the small functions internal — exported only where their own unit +tests need them. A shallow interface (one whose surface is nearly as complex +as its implementation) pushes a correctness obligation onto every call site; +a deep one absorbs it once. Apply the deletion test when unsure: if deleting +the wrapper would concentrate the composition logic back into every caller +rather than just relocate it, the wrapper is worth having. + +Worked example: `src/application/ingestion/` exposes `parse_and_chunk_document` +as its only caller-facing entry point. It dispatches on source type and owns +the `anyio.to_thread.run_sync` + `CapacityLimiter` offload ADR-0017 requires; +`parse_docx`/`parse_csv`/`parse_xlsx`/`chunk_document` stay in the package, +exported mainly for their own tests, not for outside callers to reach for +directly. Follow this pattern in `application/` as new packages are added +there — `points/`, `retrieval/`, `threads/` — rather than exposing their +internals as the primary surface. + ### Resource lifetime rules (ADR-0012) - Application-lifetime objects (SQLAlchemy engine/sessionmaker, Qdrant client,