- HEXAGONAL_ARCHITECTURE_zh.md becomes the spec: Cockburn/AWS-sourced
standard structure (domain seven-piece layout, file naming rules),
the four-transformation conversion chain with fixed owners and method
names, commands/events design with upgrade triggers, an enforced rule
table, and generic read/write sequence + class diagrams
- FEEDBACK_DESIGN_zh.md is rewritten as the reference-implementation
walkthrough of that spec (commands, exceptions split, _apply mapping,
composition root, updated test map and pitfalls)
- add the definition and dispatch diagrams under docs/assets
HEXAGONAL_ARCHITECTURE_zh.md now carries rules only: the two orthogonal
boundaries, the AWS three-folder mapping, the two kinds of secondary
adapter, and how the boundaries are mechanically enforced. The feedback
walkthrough, its known gaps, and its todo list move to a module document,
so the guide stays readable as more modules are migrated.
Two corrections to the guide, both of which would have misled a reader:
- Ports belong inside `domain/`, not beside it. AWS places `ports/` as a
subdirectory of `domain/` and describes the domain folder as "domain and
interfaces"; lifting ports into a third top-level layer would make the
domain depend on an outside package to declare its own needs. The guide
now states this explicitly, since the opposite reading is common.
- The walkthrough had the service calling RunLookup before building the
aggregate. The code does the reverse, and the order matters: validation
runs before any port call, so an invalid rating on a nonexistent run is
reported as InvalidRatingError rather than RunNotFoundError. The same
section also called that check authorization; it is referential
integrity, and authorization is the router's owner_check plus this check
taken together -- which is why the port takes no user_id.
FEEDBACK_DESIGN_zh.md is new and follows the SCHEDULE_DESIGN_zh.md shape:
the aggregate and its invariants, both ports and the conventions that
matter more than their signatures, the four use cases, both adapters, the
walkthrough, the test layering, an extension guide, and a pitfall list.
Three things it records that were not written down anywhere:
- The aggregate reads the system clock in its default factory, which
schedule deliberately avoids. Acceptable while the timestamp is only a
bookkeeping stamp and feeds no rule; noted with the condition that would
force a change.
- A repository that explicitly inherits its Protocol turns a misspelled
method into a silent None, because the inherited body is `...`. Hit for
real during the move. isinstance() cannot detect it, so asserting "the
port is satisfied" is not a substitute for asserting return values.
- RunLookup has no contract test against a real RunStore. A renamed key in
the dict RunStore.get() returns would turn every rating into a 404 with
the suite still green.
README.md indexes both under Quick Links, next to ARCHITECTURE.md.