The Hexagon · 依賴反轉
一個模組 = 三個同心環
內圈是純業務、外圈是技術細節。箭頭永遠由外向內 —— adapters → application → domain。
Domain
entities · exceptions · lifecycle
零框架 · 不 import 任何外環
Application
Port = Protocol 契約
UseCase = 一個業務流程
Inbound Adapter
FastAPI router · FastStream 訂閱者
驅動 use case
Outbound Adapter
SQLAlchemy repo · httpx · S3
實作 Port
R1 · Domain purity ── application / domain 不得 import fastapi · sqlalchemy · pydantic · shared.infrastructure
Request journey · api 行程
一個 HTTP 請求的旅程
綠點 = 請求,由外層中介一路穿到 Postgres,再沿 session_context commit。
HTTP Client
中介層 MiddlewareCORS → RedMetrics → TenantActive → Idempotency
Inbound Routeradapters/inbound/api · FastAPI
AuthorizedUseCasePDP 權限檢查 · 拒絕 → 403 + 稽核
AuditedUseCase成功 → 同交易寫 audit_log
Use Caseapplication/use_cases · 一個流程
PortProtocol — 依賴反轉點
SqlAlchemy Repositoryadapters/outbound · RLS set_local app.tenant_id
PostgreSQLvia PgBouncer · transaction pool
src/shared · 橫切基板
shared 提供什麼共享能力
通用能力集中一處,模組往內依賴即可 —— 但方向嚴格單向。
Ports & Adapters(通用)
ClockIdGenerator · uuid7Encryptor · AES-GCM
OutboundHttpClient · httpxObjectStorage · S3
DomainEventPublisherBackgroundJobSubmitter · outboxDistributedLock · Garnet
Use-case 裝飾器
AuditedUseCaseAuthorizedUseCase
Infrastructure & 統一信封
session_context / get_dbRLSCacheService
post-commit bufferDomainError + ErrorCodes{data, messages, status_code}
R3d · 黃金鐵律 ── src.shared 永不 import src.modules
SPI Seam · 反轉跨模組依賴
shared 需要 module 時怎麼辦
shared 開「插槽」、模組在組合根把「插頭」插進去。以 PDP 權限判定為例。
shared · get_pdp()placeholder → raise NotImplementedError
啟動時以 dependency_overrides 覆寫
組合根 · main.pywire_pdp(app, build_pdp_dependency)
permission · SqlAlchemyPdp無狀態 PDP · 展開 token roles claim 純比對
同型接縫:wire_tenant_context · wire_entitlement_service · MODULE_REGISTRY
src/modules · 分層與邊界
模組分層與匯入方向
Feature 可依賴 Platform,反之不行;同層 sibling 互相 import 也禁止。
Feature tier · 特徵模組選配 / 可訂閱
llmstorage
projectdirectorycontract
↓ 可 import ✓
↑ 反向 import ✗
Platform / core tier · 平台層禁撤銷
identitytenantpermission
跨模組協作 ── 只經 __init__.py 契約;真正協作用「消費者自持 Port + 組合根注入」,模組彼此零直接 import(例:tenant 的 OwnerInvitationIssuer ← identity)