↑ ↓ 移动 · Enter 打开 · Esc 关闭点按一个结果

架构
skill · design 类 · opus

domain-modeling

根据用例对聚合、不变量、端口和事件建模。

skill · designopus写入 10-dominio.md仅限 docs/use-cases/UC-*/**
§ 它写出的文件

10-dominio.md,逐块说明

一份真实的分文件:bank-app 中的 UC-001-create-customer,为节选。把鼠标移到某一块上或点击它,可以看到 skill 在这里固定了什么。文件的各个章节也是标签:点击即可跳转。

bank-app/docs/use-cases/UC-001-create-customer/10-dominio.md

UC-001 · Domain and application

Partial of docs/use-cases/UC-001-create-customer/. Owner: domain-modeling. Inherits the canonical names from 00-caso-de-uso.md — doesn't reinvent them. Contains no code. Form references in .claude/skills/domain-modeling/templates/*.java.example.
FieldValue
Mother spec00-caso-de-uso.md
AggregateCustomer
Divergences from the mother specnone — 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.

FieldTypeWhy this type
idCustomerIdValue object over UUID (v7). Prevents passing another aggregate's id in its place
nameStringFree text — counter-catalog of @.claude/rules/value-objects.md. Trim, not blank and 2 to 120 characters are invariants of Customer
securityNumberSecurityNumberFormation rule (exactly 11 digits) and own behaviour (masked()) — criteria 1 and 4
birthDateLocalDatejava.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
registeredAtInstantCreation instant, from the injected Clock

Value objects

VOWrapsOwn invariantFactory
CustomerIdUUIDnot nullCustomerId.of(UUID)
SecurityNumberStringnot 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 strippedSecurityNumber.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.

#InvariantWhere it's enforcedExceptionerrorCode
1id is presentCustomer compact constructorValidationExceptionCUSTOMER_ID_REQUIRED
2name is present and not blank after trimmingCustomer compact constructorValidationExceptionCUSTOMER_NAME_REQUIRED
3name, trimmed, has 2 to 120 characters; the trimmed value is storedCustomer compact constructorValidationExceptionCUSTOMER_NAME_LENGTH
4securityNumber is presentSecurityNumber compact constructor / Customer compact constructorValidationExceptionSECURITY_NUMBER_REQUIRED
5securityNumber is exactly 11 digits 0–9SecurityNumber compact constructorValidationExceptionSECURITY_NUMBER_INVALID
6birthDate is presentCustomer compact constructorValidationExceptionBIRTH_DATE_REQUIRED
7birthDate is not after todayCustomer.registerValidationExceptionBIRTH_DATE_IN_FUTURE
8completed age at most 130: birthDate.plusYears(131) is after todayCustomer.registerValidationExceptionBIRTH_DATE_TOO_OLD
9strictly over 18: birthDate.plusYears(18) is before today (equal is rejected)Customer.registerBusinessRuleViolationExceptionCUSTOMER_UNDERAGE
10registeredAt is presentCustomer compact constructorValidationExceptionREGISTERED_AT_REQUIRED
11securityNumber is unique in the systemoutside the aggregate — CreateCustomerUseCase checks through CustomerRepository.existsBySecurityNumber; the unique constraint closes the race and the adapter translates its violationSecurityNumberAlreadyRegisteredException (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:

  1. builds SecurityNumber.of(command.securityNumber()) (invariants 4-5);
  2. customerRepository.existsBySecurityNumber(securityNumber) → true raises
  3. Customer.register(new id, command.name(), securityNumber, command.birthDate(), clock)
  4. customerRepository.save(customer) and returns its id().

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.

FileTypeState
domain/model/CustomerId.javarecord VONEW
domain/model/SecurityNumber.javarecord VONEW
domain/model/Customer.javarecord aggregateNEW
domain/exception/DomainException.javaabstract classNEW
domain/exception/NotFoundException.javaclassNEW
domain/exception/ValidationException.javaclassNEW
domain/exception/BusinessRuleViolationException.javaclassNEW
domain/exception/ConflictException.javaclassNEW
domain/exception/SecurityNumberAlreadyRegisteredException.javaclass, extends ConflictExceptionNEW
application/usecase/customer/CreateCustomerCommand.javarecordNEW
application/usecase/customer/CreateCustomerUseCase.javaclassNEW
application/port/CustomerRepository.javainterfaceNEW

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.

在 GitHub 上打开完整文件 →
§ 行为

它读什么、写什么、拒绝什么

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 与 hook

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
§ 编排

它在轨道上的位置

每张卡片是 /new-feature 的一步;箭头带着一步交给下一步的分文件。虚线的步骤仅在用例需要时运行。

/new-feature-implement 之后谁读这份分文件

批准之后,/new-feature-implement 让执行器构建代码。spec 按引用整合,与分文件不一致时以 spec 为准;分文件按章节读取,用于细节。

10-dominio.mddocs/use-cases/UC-NNN-…/
按章节读取,用于细节
java-spring-boot-developeragent · executor
写出代码
src/**把一个用例设计成规格,不写代码
← 返回链路