Skip to content

Commit 10094c2

Browse files
authored
docs: 增强 DDD 设计证明门禁
--delete-branch
1 parent fb0443c commit 10094c2

5 files changed

Lines changed: 129 additions & 0 deletions

File tree

skills/df-ddd-event-storming-design/SKILL.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,78 @@ Do not regenerate the whole model unless the user asks or the existing model is
239239
- Treat CQRS as a modeling perspective: commands change business state through the domain model; queries are satisfied by read models.
240240
- Read models should record their information sources, but query needs must not create domain events by themselves. A read-model-only field can come from current-state lookup, query-side joins, technical projection inputs, external sources, or accepted event payloads.
241241
- Iterate when commands, events, aggregates, or read models do not explain each other.
242+
- Treat commands, events, aggregate state, aggregate methods, and read-model fields as design claims that need proof. If the proof is only "the table/API/page has this field", downgrade the item to discovery material until a business rule or consumer proves it belongs.
243+
244+
## Design Proof Gates
245+
246+
Use these gates before finalizing commands, events, aggregates, aggregate methods, or read models. They are mandatory when reviewing or persisting a model.
247+
248+
### Event Admission Gate
249+
250+
For every accepted domain event, prove:
251+
252+
- business fact: what happened in past tense
253+
- producer: actor/external system -> command/fact input -> aggregate/process -> event
254+
- non-query consumer: actor option, command-side rule, policy/process, aggregate relationship, lifecycle, or downstream business capability affected by the fact
255+
- read-model impact, if any: which projection changes and which fields change
256+
257+
Keep brainstormed but rejected events only in `候选事件池与筛选`, not in the formal `领域事件清单` or aggregate event list. If an item only repairs a projection, display, report, cache, operation log, or troubleshooting view, reject it as a domain event and record the alternative source.
258+
259+
### Command Granularity Gate
260+
261+
Design commands from actor intent or external fact input boundaries, not from permutations of possible events.
262+
263+
- One command may produce multiple events when those outcomes belong to the same actor intent and consistency boundary.
264+
- Do not create separate commands merely because different event combinations are possible.
265+
- Do not create one "apply/sync/update all" command unless the initiating actor or external system is actually submitting one authoritative fact package and the aggregate can make the decision coherently.
266+
- When a command can produce multiple events, explain why the events are the result of one business decision instead of independent actions.
267+
268+
### Aggregate State Necessity Gate
269+
270+
For every aggregate state item or property, prove at least one direct use:
271+
272+
- a command/business method reads it to decide whether an action is allowed
273+
- an invariant or uniqueness rule depends on it
274+
- it affects event production, rejection, splitting, or payload enrichment
275+
- it represents identity, lifecycle, ownership, parentage, or another state needed to enforce consistency
276+
277+
If no current or confirmed future business method uses the state item, remove it from the aggregate design. Reclassify it as read-model data, persistence metadata, audit/log material, synchronization metadata, or unresolved discovery material.
278+
279+
### Identity And Uniqueness Gate
280+
281+
Model identity and uniqueness by actor/source, not as one generic "name/code must be unique" rule.
282+
283+
For each uniqueness rule, record:
284+
285+
- who relies on it: human actor, import operator, external master system, downstream system, or policy
286+
- identity fields and scope: tenant, company, parent, external ID, code, name, effective date, or other boundary
287+
- enforcement timing: command-side strong consistency, external acceptance check, deferred reconciliation, or query-only detection
288+
- conflict result: reject, correct, merge, override, ignore, or log-only
289+
290+
If different actors use different identity rules, keep them as separate rules even when they touch the same subject.
291+
292+
### Lifecycle Removal Gate
293+
294+
For delete, unregister, close, archive, disable, or decommission behavior, prove:
295+
296+
- the exact business meaning of removal
297+
- the real blocking business fact, not just indirect structural children
298+
- whether historical references remain valid
299+
- whether restoration exists as a real business capability; if not, do not add restore commands or events
300+
- which downstream actor, policy, rule, or read model changes after removal
301+
302+
### Read Model Source Gate
303+
304+
For every read-model field, record its source:
305+
306+
- accepted domain event payload or projection
307+
- current-state lookup from command-side persistence
308+
- query-side join
309+
- technical projection input
310+
- audit/log/troubleshooting source
311+
- external read source
312+
313+
Do not add a domain event only to populate a read-model field. A missing read-model field is a sourcing question first and a domain-event gap only when a non-query business consumer exists.
242314

243315
## Traceability Rules
244316

@@ -534,13 +606,19 @@ Before finalizing, check:
534606
- Every important event has an affected actor, subject, command-side rule, policy/process, aggregate, or downstream business relationship.
535607
- Current-domain state-changing events are published by aggregates unless a clear exception is recorded.
536608
- Every command has the information it needs from command fields plus aggregate state/history.
609+
- Every command is justified by actor intent or an external fact input boundary, not by an arbitrary event-combination permutation.
610+
- If one command may publish multiple events, those events are explained as outcomes of one business decision inside one consistency boundary.
537611
- Meaningful command outcomes are represented as events.
538612
- Generic events such as `信息已变更` have been replaced by specific facts or explicitly justified.
613+
- Rejected candidate events are not retained in accepted event catalogs, aggregate event lists, or implementation-alignment lists.
539614
- Every read model records whether each field comes from accepted events, current-state lookup, query-side joins, technical projection inputs, enriched payloads, or external read sources.
540615
- Every read-model-only field is marked as query-side data, technical projection input, current-state lookup, audit/log material, or external-source data instead of being promoted to a domain event.
541616
- Policies do not hide aggregate invariants.
542617
- Domain services do not become procedural service layers.
543618
- Aggregates are derived from behavior, rules, identity, lifecycle, and invariants, not tables, CRUD resources, or noun lists.
619+
- Every aggregate property is directly used by at least one command/business method, invariant, uniqueness rule, event production decision, lifecycle decision, or consistency relationship.
620+
- Human-maintained identity rules and external-system identity rules are separated when they use different scopes or keys.
621+
- Removal/decommission rules identify the real blocking business fact, historical-reference behavior, and downstream consequence.
544622
- Aggregates are neither too large nor too fragmented.
545623
- Query needs do not pollute aggregate state.
546624

@@ -560,6 +638,11 @@ Refuse or correct these patterns:
560638
- Treating external master-data sync as a simple upsert when ordering, dependency, failure, or conflict resolution has business meaning.
561639
- Creating domain events for technical actions the business does not care about.
562640
- Creating domain events only because a read model, page, report, cache, or projection needs a field refreshed.
641+
- Keeping rejected candidate events in final event lists, aggregate files, or implementation checklists.
642+
- Creating command permutations from possible event combinations instead of actor intent or external fact input.
643+
- Adding "source type", "current status", "description", display names, or other fields to aggregate state when no aggregate method or invariant uses them.
644+
- Collapsing different identity rules into one generic uniqueness statement when human actors and upstream systems use different scopes or keys.
645+
- Adding restore behavior because it is technically possible when no business capability exists.
563646
- Putting report/page/query fields into aggregates for convenience.
564647
- Putting most business rules into command handlers, application services, listeners, repositories, or policies.
565648
- Splitting bounded contexts before understanding the current problem domain.

skills/df-ddd-event-storming-design/references/anti-patterns.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
- Upsert sync modeling: treat external master-data synchronization as a simple create/update when the business cares about dependency ordering, conflict handling, missing parent records, retries, or failure status.
1515
- Read model leakage: add page/report fields to aggregate state only because a query needs them.
1616
- Projection-driven event: create a domain event only because a read model, page, report, cache, or projection needs a field refreshed.
17+
- Rejected candidate leakage: keep brainstormed-but-rejected events in final event catalogs, aggregate event lists, or implementation-alignment checklists.
18+
- Command permutation explosion: create commands from possible event combinations rather than actor intent, external fact input, and consistency boundary.
19+
- Unused aggregate state: add properties to an aggregate because a table, API, projection, or UI has them even though no aggregate method, invariant, uniqueness rule, lifecycle rule, or event decision uses them.
20+
- Generic uniqueness rule: collapse actor-specific identity rules into a single "name/code must be unique" statement when human maintenance and upstream systems use different scopes, keys, or conflict handling.
21+
- Invented restoration: add restore commands/events only because deletion is modeled, without a confirmed restoration business capability.
22+
- Indirect removal guard: block removal by structural children rather than the real business fact that matters to the domain.
1723
- Framework-shaped domain: treat controllers, DTOs, repositories, or packages as the domain model.
1824
- Unproduced event: keep a domain event without a command, policy, process, or external fact that can produce it.
1925
- Unexplained read source: define a read model field without identifying whether it comes from accepted events, current-state lookup, query-side joins, technical projection input, enriched payloads, audit/log data, external sources, or a confirmed domain-event gap.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# 问题域边界
2+
3+
后台目录管理。
4+
5+
# 领域事件清单
6+
7+
- 名称已修订,不进入领域事件,只用于展示字段。
8+
- 外部事实已拒绝,不进入领域事件,排查走日志。
9+
10+
# 命令清单
11+
12+
- 按事件组合创建命令:名称变更加状态变更命令、名称变更加除册命令。
13+
14+
# 聚合设计
15+
16+
- 聚合属性:来源类型、当前状态、描述。
17+
- 这些属性目前无业务方法使用,当前只是展示和同步排查。
18+
19+
# 完整性检查
20+
21+
- 名称不能重复。

skills/df-ddd-event-storming-design/scripts/run-design-examples.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ const CASES = [
5050
file: "artifact_flooding.md",
5151
expectErrors: true,
5252
},
53+
{
54+
name: "proof_gate_violations",
55+
file: "proof_gate_violations.md",
56+
expectErrors: true,
57+
},
5358
];
5459

5560
describe("DDD Design Validation", () => {

skills/df-ddd-event-storming-design/scripts/validate-ddd-design.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ const SMELLS: Record<string, string[]> = {
3636
"(Company|Dept|Department|Position|Employee)(Created|Updated|Deleted|Edited)Event",
3737
"(公司|部门|岗位|员工).*(新增|修改|删除|编辑).*事件",
3838
],
39+
rejected_candidate_leakage: [
40+
"(不进入领域事件|拒绝|淘汰|降级|候选事件).{0,80}(领域事件清单|聚合事件|事件列表|最终事件)",
41+
],
42+
command_permutation: [
43+
"(事件组合|排列组合|不同组合|每种组合).{0,80}(命令|cmd|command)",
44+
"(命令|cmd|command).{0,80}(事件组合|排列组合|不同组合|每种组合)",
45+
],
46+
unused_aggregate_state: [
47+
"(聚合属性|聚合状态|状态字段|属性清单)[\\s\\S]*(来源类型|sourceType|当前状态|currentStatus|描述|description)[\\s\\S]*(未使用|无业务方法|仅展示|只展示)",
48+
"(来源类型|sourceType|当前状态|currentStatus|描述|description).{0,80}(聚合属性|聚合状态)",
49+
],
50+
generic_uniqueness: [
51+
"^(?![\\s\\S]*(范围|scope|租户|公司|父级|上游|人工|来源|actor))[\\s\\S]*(名称|编码|name|code).{0,20}(不能重复|唯一|冲突)",
52+
],
3953
};
4054

4155
const REQUIRED_SECTIONS = [

0 commit comments

Comments
 (0)