10-dominio.md,逐块说明
一份真实的分文件:bank-app 中的 UC-001-create-customer,为节选。把鼠标移到某一块上或点击它,可以看到 skill 在这里固定了什么。文件的各个章节也是标签:点击即可跳转。
UC-001 · Domain and application
Partial ofdocs/use-cases/UC-001-create-customer/. Owner:domain-modeling. Inherits the canonical names from00-caso-de-uso.md— doesn't reinvent them. Contains no code. Form references in.claude/skills/domain-modeling/templates/*.java.example.
| Field | Value |
|---|---|
| Mother spec | 00-caso-de-uso.md |
| Aggregate | Customer |
| Divergences from the mother spec | none — the open rows of its component table are closed here: name stays String, birthDate stays LocalDate, today's date comes from an injected java.time.Clock |
Package base: dev.nerviz.bankapp. Paths below are relative to src/main/java/dev/nerviz/bankapp/.
## 1 · Aggregate and value objects
Aggregate root: Customer. Boundary: the customer alone — no reference to another aggregate.
| Field | Type | Why this type |
|---|---|---|
id | CustomerId | Value object over UUID (v7). Prevents passing another aggregate's id in its place |
name | String | Free text — counter-catalog of @.claude/rules/value-objects.md. Trim, not blank and 2 to 120 characters are invariants of Customer |
securityNumber | SecurityNumber | Formation rule (exactly 11 digits) and own behaviour (masked()) — criteria 1 and 4 |
birthDate | LocalDate | java.time is already an immutable value with a unit; not wrapped (value-objects.md § Numbers). The age rules depend on today and live in Customer.register |
registeredAt | Instant | Creation instant, from the injected Clock |
Value objects
| VO | Wraps | Own invariant | Factory |
|---|---|---|---|
CustomerId | UUID | not null | CustomerId.of(UUID) |
SecurityNumber | String | not null; exactly 11 characters, each 0–9; no check-digit validation (mother spec, user's decision). of trims surrounding whitespace only — no mask removal: punctuation is rejected, not stripped | SecurityNumber.of(String) |
SecurityNumber exposes value() (the 11 digits, for persistence mapping) and masked() (********* followed by the last 2 digits). Its toString() returns masked(), so a SecurityNumber concatenated into a log line or an exception message never prints the full value.
All record, immutable, validation in the compact constructor. Shape: templates/ValueObject.java.example.
… 此处截断,其余见文件
## 2 · Invariants
Exception family: NEW — five classes in the domain.exception package: DomainException (abstract, errorCode required), NotFoundException, ValidationException, BusinessRuleViolationException, ConflictException. Shapes in templates/DomainException.java.example and the four typed ones. NotFoundException is unused by this case and still created: the family is created once, whole.
| # | Invariant | Where it's enforced | Exception | errorCode |
|---|---|---|---|---|
| 1 | id is present | Customer compact constructor | ValidationException | CUSTOMER_ID_REQUIRED |
| 2 | name is present and not blank after trimming | Customer compact constructor | ValidationException | CUSTOMER_NAME_REQUIRED |
| 3 | name, trimmed, has 2 to 120 characters; the trimmed value is stored | Customer compact constructor | ValidationException | CUSTOMER_NAME_LENGTH |
| 4 | securityNumber is present | SecurityNumber compact constructor / Customer compact constructor | ValidationException | SECURITY_NUMBER_REQUIRED |
| 5 | securityNumber is exactly 11 digits 0–9 | SecurityNumber compact constructor | ValidationException | SECURITY_NUMBER_INVALID |
| 6 | birthDate is present | Customer compact constructor | ValidationException | BIRTH_DATE_REQUIRED |
| 7 | birthDate is not after today | Customer.register | ValidationException | BIRTH_DATE_IN_FUTURE |
| 8 | completed age at most 130: birthDate.plusYears(131) is after today | Customer.register | ValidationException | BIRTH_DATE_TOO_OLD |
| 9 | strictly over 18: birthDate.plusYears(18) is before today (equal is rejected) | Customer.register | BusinessRuleViolationException | CUSTOMER_UNDERAGE |
| 10 | registeredAt is present | Customer compact constructor | ValidationException | REGISTERED_AT_REQUIRED |
| 11 | securityNumber is unique in the system | outside the aggregate — CreateCustomerUseCase checks through CustomerRepository.existsBySecurityNumber; the unique constraint closes the race and the adapter translates its violation | SecurityNumberAlreadyRegisteredException (extends ConflictException) | SECURITY_NUMBER_ALREADY_REGISTERED |
Order inside register: 7, then 8, then 9 — a future date reports itself as such, not as underage. plusYears on 29 February yields 28 February in a non-leap year (mother spec).
… 此处截断,其余见文件
## 3 · Ports
Input — clean architecture: no interface. The concrete use case is the input boundary.
application/usecase/customer/CreateCustomerUseCase.java
class CreateCustomerUseCase
CreateCustomerUseCase(CustomerRepository customerRepository, Clock clock)
CustomerId create(CreateCustomerCommand command)
create:
- builds
SecurityNumber.of(command.securityNumber())(invariants 4-5); customerRepository.existsBySecurityNumber(securityNumber)→trueraisesCustomer.register(new id, command.name(), securityNumber, command.birthDate(), clock)customerRepository.save(customer)and returns itsid().
The transaction opens and closes on this method (@.claude/rules/architecture-ddd.md § Application) — the annotation lives on this application-layer class. Constructor injection, private final fields. Throws ValidationException, BusinessRuleViolationException and SecurityNumberAlreadyRegisteredException.
Validating the format (step 1) before the uniqueness lookup keeps a malformed number from reaching the database query.
Command — application/usecase/customer/CreateCustomerCommand.java
… 此处截断,其余见文件
## 4 · Events
none — the mother spec fixes no publication: no other system needs to learn that a customer was created. No CustomerCreated is designed; a future case that needs it adds it in its own ## Impact on approved use cases.
## 5 · Components to create
Refines the 00-caso-de-uso.md rows marked Detailed by: domain-modeling.
| File | Type | State |
|---|---|---|
domain/model/CustomerId.java | record VO | NEW |
domain/model/SecurityNumber.java | record VO | NEW |
domain/model/Customer.java | record aggregate | NEW |
domain/exception/DomainException.java | abstract class | NEW |
domain/exception/NotFoundException.java | class | NEW |
domain/exception/ValidationException.java | class | NEW |
domain/exception/BusinessRuleViolationException.java | class | NEW |
domain/exception/ConflictException.java | class | NEW |
domain/exception/SecurityNumberAlreadyRegisteredException.java | class, extends ConflictException | NEW |
application/usecase/customer/CreateCustomerCommand.java | record | NEW |
application/usecase/customer/CreateCustomerUseCase.java | class | NEW |
application/port/CustomerRepository.java | interface | NEW |
Requirement for the wiring, not a domain class: a java.time.Clock bean (Clock.systemUTC()) in infrastructure/config/ClockConfig.java — NEW, none exists. Written by the executor with the configuration; recorded here because the use case's constructor needs it.
## Design patterns
none — no force in the spec, no symptom on disk. The static factories register / rehydrate are the naming rule's creation convention (@.claude/rules/naming.md § Methods), not a catalog pattern; nothing in the mother spec enumerates variants or rules per type, and src/main/java holds no domain or application class yet.
## Impact on approved use cases
none — UC-001 is the first case.
它读什么、写什么、拒绝什么
skill 自己的契约,取自 SKILL.md,保留原文。
Class
design — the territory is skill_classes.design in @.claude/schemas/extensions.json and ArchHook.java guard enforces it. Writes inside the use case folder and nothing else; a write anywhere outside is refused with exit 2.
Reads
docs/use-cases/UC-NNN-<slug>/00-use-case.md (mandatory — stops without it), @.claude/rules/architecture-ddd.md (Domain and Application sections), @.claude/rules/value-objects.md (criterion for which field becomes a value object), @.claude/rules/naming.md, @.claude/rules/error-handling.md, @.claude/rules/code-quality.md, @.claude/rules/logging.md (which fields to flag as masking candidates), @.claude/rules/spec-diagrams.md (the diagrams of block 1) with references/mermaid-class-and-state.md, @.claude/rules/authorization.md § Boundary (only when Access is the owner — step 4c), @.claude/rules/spec-external-facts.md (an identifier or value of another system that a domain name or invariant states carries a source), and the active blueprint's packages.map.
Writes
docs/use-cases/UC-NNN-<slug>/10-domain.md. Only that file.
Does not write Java code
The classes in domain/ and application/ come from the executor agent — the exception family included: this skill owns the shape (the templates/ exemplars) and the state (NEW or REUSE, in the partial), not the writing. Does not touch 00-use-case.md (use-case-design), the other partials, or .claude/rules/**.
Does not collide with `gof-design-patterns`
That skill owns the catalog and, invoked standalone, refactors code that already hurts; this one decides which catalog row shapes the domain of the case being designed, and writes the decision into the partial — step 5b. Different artifacts, one owner of each.
Does not
decide persistence, mapping, or transport technology — 20-persistence.md and 30-rest.md have their own owners.
来源:SKILL.md § Contract(nerviz e4cae27)。
guard 如何处理它
design 是这个 skill 的类。ArchHook 在 skill 被调用时开启一个阶段,并按该类的领地检查每次写入。右侧文字是 hook 自己的输出。
design 类的 write_allow
- ✓
docs/use-cases/UC-*/** - ✓
docs/use-cases/BACKLOG.md src/**:迁移和类归执行器docker-compose.yml:另一个类的文件已批准的用例:其 spec 已冻结
拒绝以退出码 2 结束,消息回到模型。
PreToolUse Skill(domain-modeling) audit call exit 0 guard call exit 0 # a skill of another class replaces the phase # write_allow: docs/use-cases/UC-*/**, docs/use-cases/BACKLOG.md
PreToolUse Write(docs/use-cases/UC-005-record-kyc-verdict/10-dominio.md) guard write exit 0 PostToolUse audit file exit 0
PreToolUse Write(docker-compose.yml) guard write exit 2 ❌ `domain-modeling` is class `design` — docker-compose.yml is outside its territory. write_allow: docs/use-cases/UC-*/**, docs/use-cases/BACKLOG.md Put the content in the spec as a code block, or finish the run and invoke the skill that owns this path. If the path is legitimately this skill's, widen skill_classes.design in .claude/schemas/extensions.json — retrying will not help.
# UC-005-record-kyc-verdict-spec.md: status: approved PreToolUse Edit(docs/use-cases/UC-005-record-kyc-verdict/10-dominio.md) guard write exit 2 ❌ UC-005-record-kyc-verdict is approved — its specs are immutable. Record the change in the new use case's "Impact on approved use cases" section, and the one-line entry in UC-005-record-kyc-verdict/CHANGELOG.md, which stays writable.
它在轨道上的位置
每张卡片是 /new-feature 的一步;箭头带着一步交给下一步的分文件。虚线的步骤仅在用例需要时运行。
/new-feature orchestrator · opus/new-feature-implement 之后谁读这份分文件批准之后,/new-feature-implement 让执行器构建代码。spec 按引用整合,与分文件不一致时以 spec 为准;分文件按章节读取,用于细节。