dflow-sdd-ddd 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.en.md +73 -48
- package/README.md +46 -36
- package/TEMPLATE-COVERAGE.md +0 -1
- package/TEMPLATE-LANGUAGE-GLOSSARY.md +1 -0
- package/bin/dflow.js +7 -11
- package/docs/evaluating-dflow.en.md +11 -7
- package/docs/evaluating-dflow.md +9 -4
- package/docs/using-with-claude-code.en.md +40 -23
- package/docs/using-with-claude-code.md +34 -23
- package/docs/using-with-codex.en.md +125 -42
- package/docs/using-with-codex.md +93 -34
- package/docs/using-with-github-copilot.en.md +135 -34
- package/docs/using-with-github-copilot.md +120 -43
- package/docs/why-dflow.en.md +72 -0
- package/docs/why-dflow.md +72 -0
- package/lib/init.js +867 -214
- package/package.json +2 -2
- package/templates/brownfield/references/drift-verification.md +41 -10
- package/templates/brownfield/references/finish-feature-flow.md +3 -2
- package/templates/brownfield/references/git-integration.md +0 -1
- package/templates/brownfield/references/init-project-flow.md +31 -17
- package/templates/brownfield/references/modify-existing-flow.md +44 -38
- package/templates/brownfield/references/new-feature-flow.md +41 -11
- package/templates/brownfield/references/new-phase-flow.md +9 -2
- package/templates/brownfield/references/pr-review-checklist.md +7 -1
- package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +258 -29
- package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +5 -8
- package/templates/brownfield/scaffolding/_conventions.md +10 -9
- package/templates/brownfield/templates/_index.md +1 -1
- package/templates/brownfield/templates/context-map.md +12 -4
- package/templates/brownfield/templates/lightweight-spec.md +1 -1
- package/templates/brownfield/templates/phase-spec.md +1 -1
- package/templates/common/references/ddd-modeling-guide.md +643 -0
- package/templates/common/skill/SKILL.md +9 -6
- package/templates/greenfield/references/drift-verification.md +60 -15
- package/templates/greenfield/references/finish-feature-flow.md +3 -2
- package/templates/greenfield/references/git-integration.md +0 -1
- package/templates/greenfield/references/init-project-flow.md +31 -17
- package/templates/greenfield/references/modify-existing-flow.md +5 -7
- package/templates/greenfield/references/new-feature-flow.md +49 -19
- package/templates/greenfield/references/new-phase-flow.md +5 -2
- package/templates/greenfield/references/pr-review-checklist.md +9 -1
- package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +221 -29
- package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +10 -15
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +1 -1
- package/templates/greenfield/scaffolding/_conventions.md +9 -8
- package/templates/greenfield/templates/_index.md +1 -1
- package/templates/greenfield/templates/aggregate-design.md +6 -0
- package/templates/greenfield/templates/context-map.md +13 -4
- package/templates/greenfield/templates/events.md +4 -1
- package/templates/greenfield/templates/lightweight-spec.md +1 -1
- package/templates/greenfield/templates/phase-spec.md +1 -1
- package/docs/migrating-to-dflow-v1.md +0 -230
- package/templates/brownfield/templates/CLAUDE.md +0 -165
- package/templates/greenfield/references/ddd-modeling-guide.md +0 -351
- package/templates/greenfield/templates/CLAUDE.md +0 -172
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# DDD Modeling Guide
|
|
2
|
-
|
|
3
|
-
When a developer asks "How should I model X?" or is designing domain structures,
|
|
4
|
-
use this guide to walk them through DDD tactical patterns.
|
|
5
|
-
|
|
6
|
-
## The Modeling Conversation
|
|
7
|
-
|
|
8
|
-
Start with the business, not the code:
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
"Let's forget about databases and classes for a moment.
|
|
12
|
-
Tell me: what are the rules? What must always be true?
|
|
13
|
-
What can never happen?"
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
These invariants drive the entire model design.
|
|
17
|
-
|
|
18
|
-
## Pattern Selection Flowchart
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
Is it identified by an ID that persists over time?
|
|
22
|
-
│
|
|
23
|
-
├─ Yes → Is it the "boss" that protects a consistency boundary?
|
|
24
|
-
│ ├─ Yes → AGGREGATE ROOT
|
|
25
|
-
│ └─ No → ENTITY (belongs inside an Aggregate)
|
|
26
|
-
│
|
|
27
|
-
└─ No → Is it defined entirely by its properties?
|
|
28
|
-
├─ Yes → VALUE OBJECT
|
|
29
|
-
└─ No → Does it represent an operation spanning multiple Aggregates?
|
|
30
|
-
├─ Yes → DOMAIN SERVICE
|
|
31
|
-
└─ No → Re-examine — it's probably one of the above
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Aggregate Design
|
|
35
|
-
|
|
36
|
-
Aggregates are the most important and most commonly misunderstood DDD concept.
|
|
37
|
-
|
|
38
|
-
### What is an Aggregate?
|
|
39
|
-
|
|
40
|
-
A cluster of objects treated as a single unit for data changes. The Aggregate Root
|
|
41
|
-
is the only entry point — outside code cannot reach inside and modify child entities
|
|
42
|
-
or value objects directly.
|
|
43
|
-
|
|
44
|
-
### Aggregate Design Rules
|
|
45
|
-
|
|
46
|
-
1. **Protect invariants** — The Aggregate exists to enforce business rules that span
|
|
47
|
-
multiple objects within it.
|
|
48
|
-
|
|
49
|
-
2. **One Aggregate per transaction** — A single operation should modify only ONE
|
|
50
|
-
Aggregate. If you need to modify two Aggregates, use Domain Events for eventual
|
|
51
|
-
consistency.
|
|
52
|
-
|
|
53
|
-
3. **Reference other Aggregates by ID only** — Never hold a direct object reference
|
|
54
|
-
to another Aggregate. Store its ID instead.
|
|
55
|
-
|
|
56
|
-
4. **Keep them small** — Large Aggregates cause concurrency issues. If two users can
|
|
57
|
-
independently modify different parts, those parts should probably be separate Aggregates.
|
|
58
|
-
|
|
59
|
-
### Example: Expense Report Aggregate
|
|
60
|
-
|
|
61
|
-
```csharp
|
|
62
|
-
// ExpenseReport is the Aggregate Root
|
|
63
|
-
public class ExpenseReport : AggregateRoot
|
|
64
|
-
{
|
|
65
|
-
private readonly List<ExpenseLineItem> _lineItems = new();
|
|
66
|
-
|
|
67
|
-
public EmployeeId SubmittedBy { get; private set; } // Reference by ID
|
|
68
|
-
public ReportPeriod Period { get; private set; } // Value Object
|
|
69
|
-
public Money TotalAmount => CalculateTotal(); // Derived
|
|
70
|
-
public ReportStatus Status { get; private set; } // Value Object (enum-like)
|
|
71
|
-
|
|
72
|
-
// State change through explicit methods — not property setters
|
|
73
|
-
public void AddLineItem(string description, Money amount, ExpenseCategory category)
|
|
74
|
-
{
|
|
75
|
-
// Enforce invariants
|
|
76
|
-
if (Status != ReportStatus.Draft)
|
|
77
|
-
throw new DomainException("Cannot add items to a submitted report.");
|
|
78
|
-
|
|
79
|
-
if (_lineItems.Count >= 50)
|
|
80
|
-
throw new DomainException("Maximum 50 line items per report.");
|
|
81
|
-
|
|
82
|
-
var lineItem = new ExpenseLineItem(description, amount, category);
|
|
83
|
-
_lineItems.Add(lineItem);
|
|
84
|
-
|
|
85
|
-
// Raise domain event
|
|
86
|
-
AddDomainEvent(new LineItemAddedEvent(Id, lineItem.Id, amount));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public void Submit()
|
|
90
|
-
{
|
|
91
|
-
if (Status != ReportStatus.Draft)
|
|
92
|
-
throw new DomainException("Only draft reports can be submitted.");
|
|
93
|
-
|
|
94
|
-
if (!_lineItems.Any())
|
|
95
|
-
throw new DomainException("Cannot submit an empty report.");
|
|
96
|
-
|
|
97
|
-
Status = ReportStatus.Submitted;
|
|
98
|
-
AddDomainEvent(new ExpenseReportSubmittedEvent(Id, SubmittedBy, TotalAmount));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Design Questions to Ask
|
|
104
|
-
|
|
105
|
-
When designing a new Aggregate:
|
|
106
|
-
|
|
107
|
-
1. **What are the invariants?** What business rules must ALWAYS be true?
|
|
108
|
-
2. **What's the consistency boundary?** What must be updated atomically?
|
|
109
|
-
3. **What can change independently?** Separate Aggregates for separate concerns.
|
|
110
|
-
4. **Who modifies this?** How many concurrent users? (Affects Aggregate size)
|
|
111
|
-
5. **What events does this produce?** What other parts of the system need to know?
|
|
112
|
-
|
|
113
|
-
### Set-Based / Uniqueness Invariants
|
|
114
|
-
|
|
115
|
-
Some invariants are not about one Aggregate but about a **set selected by a
|
|
116
|
-
business key or status**: "email (normalized) is unique across all Users", "each
|
|
117
|
-
seat holds at most one active booking", "each connector has at most one
|
|
118
|
-
in-progress charging session". A single Aggregate instance cannot see the rest of
|
|
119
|
-
that set, so it cannot enforce the rule on its own.
|
|
120
|
-
|
|
121
|
-
Handle them the same way **regardless of which Aggregate boundary you choose**:
|
|
122
|
-
|
|
123
|
-
1. **As separate Aggregates** (e.g. `User` and `Booking` are distinct): the
|
|
124
|
-
Application layer *orchestrates* the check — via a repository query, a
|
|
125
|
-
Specification, or a domain service (the rule stays domain-named; it is not an
|
|
126
|
-
inline `if-else` in the command handler) — and the **database enforces it with
|
|
127
|
-
a unique / partial (filtered) unique index**. The DB constraint is the real
|
|
128
|
-
guarantee under concurrency; the orchestrated check just returns a friendlier
|
|
129
|
-
error first.
|
|
130
|
-
|
|
131
|
-
2. **Folded into one Aggregate** (e.g. the active session lives *inside* a
|
|
132
|
-
`Connector` as a child entity): the in-memory check (`if (Status == InUse)
|
|
133
|
-
throw …`) is logically correct, **but is still not concurrency-safe by
|
|
134
|
-
itself**. Two concurrent commands can each load the Aggregate, both pass the
|
|
135
|
-
check, and both save. Close the race with **optimistic concurrency** (a
|
|
136
|
-
`rowversion` / version token on the Aggregate root) or a DB constraint — but
|
|
137
|
-
the version check only protects you if every save actually touches the root's
|
|
138
|
-
token: inserting a child row without bumping the root's version leaves the
|
|
139
|
-
race open. Translate the resulting concurrency exception / unique violation
|
|
140
|
-
into a meaningful business conflict (e.g. HTTP 409), not a generic 500.
|
|
141
|
-
|
|
142
|
-
**Key point:** an in-memory check — at *any* layer — is never the final guarantee
|
|
143
|
-
for a uniqueness / "only one active X" rule under concurrent requests. The durable
|
|
144
|
-
enforcement is a DB unique / filtered index, an optimistic-concurrency token, or
|
|
145
|
-
an equivalent conditional write / compare-and-swap. Pick the Aggregate boundary on
|
|
146
|
-
modeling grounds (does the inner thing have an independent lifecycle / history
|
|
147
|
-
worth querying?), then add the store-level guard either way. (Heavier
|
|
148
|
-
serialization tactics — distributed locks, per-key actors, aggregate-per-key
|
|
149
|
-
sharding — exist but are advanced; reach for a store-level constraint or version
|
|
150
|
-
check first.)
|
|
151
|
-
|
|
152
|
-
## Value Objects
|
|
153
|
-
|
|
154
|
-
### When to Use Value Objects
|
|
155
|
-
|
|
156
|
-
If the answer to ALL of these is "yes", it's a Value Object:
|
|
157
|
-
- Is it defined by its properties, not by an ID?
|
|
158
|
-
- Is it immutable once created?
|
|
159
|
-
- Can two instances with the same properties be considered equal?
|
|
160
|
-
|
|
161
|
-
### Common Value Objects
|
|
162
|
-
|
|
163
|
-
```csharp
|
|
164
|
-
// Money — the classic example
|
|
165
|
-
public record Money(decimal Amount, Currency Currency)
|
|
166
|
-
{
|
|
167
|
-
public static Money Zero(Currency currency) => new(0, currency);
|
|
168
|
-
|
|
169
|
-
public Money Add(Money other)
|
|
170
|
-
{
|
|
171
|
-
if (Currency != other.Currency)
|
|
172
|
-
throw new CurrencyMismatchException(Currency, other.Currency);
|
|
173
|
-
return new Money(Amount + other.Amount, Currency);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
public Money ConvertTo(Currency target, ExchangeRate rate)
|
|
177
|
-
{
|
|
178
|
-
return new Money(rate.Convert(Amount), target);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// DateRange
|
|
183
|
-
public record DateRange(DateOnly Start, DateOnly End)
|
|
184
|
-
{
|
|
185
|
-
public DateRange
|
|
186
|
-
{
|
|
187
|
-
if (Start > End) throw new DomainException("Start must be before End.");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
public bool Contains(DateOnly date) => date >= Start && date <= End;
|
|
191
|
-
public int Days => End.DayNumber - Start.DayNumber + 1;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Currency (constrained string)
|
|
195
|
-
public record Currency
|
|
196
|
-
{
|
|
197
|
-
public string Code { get; }
|
|
198
|
-
public int DecimalPlaces { get; }
|
|
199
|
-
|
|
200
|
-
public static readonly Currency TWD = new("TWD", 0);
|
|
201
|
-
public static readonly Currency USD = new("USD", 2);
|
|
202
|
-
public static readonly Currency JPY = new("JPY", 0);
|
|
203
|
-
|
|
204
|
-
private Currency(string code, int decimalPlaces)
|
|
205
|
-
{
|
|
206
|
-
Code = code;
|
|
207
|
-
DecimalPlaces = decimalPlaces;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
public decimal Round(decimal amount) =>
|
|
211
|
-
Math.Round(amount, DecimalPlaces, MidpointRounding.AwayFromZero);
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Value Object Design Questions
|
|
216
|
-
|
|
217
|
-
1. **Does it have behavior?** Good VOs have methods, not just properties.
|
|
218
|
-
2. **Does it enforce constraints?** Constructor should reject invalid states.
|
|
219
|
-
3. **Is it reusable?** `Money` can be used across many Aggregates.
|
|
220
|
-
|
|
221
|
-
## Domain Events
|
|
222
|
-
|
|
223
|
-
### What Are Domain Events?
|
|
224
|
-
|
|
225
|
-
Something that happened in the domain that other parts of the system care about.
|
|
226
|
-
Past tense naming: `ExpenseReportSubmitted`, `LineItemAdded`, `ReportApproved`.
|
|
227
|
-
|
|
228
|
-
### When to Use Domain Events
|
|
229
|
-
|
|
230
|
-
- When one Aggregate needs to trigger changes in another Aggregate
|
|
231
|
-
- When side effects (email, notification, audit log) should happen after a domain action
|
|
232
|
-
- When different Bounded Contexts need to communicate
|
|
233
|
-
|
|
234
|
-
### Event Design
|
|
235
|
-
|
|
236
|
-
```csharp
|
|
237
|
-
public record ExpenseReportSubmittedEvent(
|
|
238
|
-
ExpenseReportId ReportId,
|
|
239
|
-
EmployeeId SubmittedBy,
|
|
240
|
-
Money TotalAmount
|
|
241
|
-
) : IDomainEvent;
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Event Flow
|
|
245
|
-
|
|
246
|
-
```
|
|
247
|
-
1. Aggregate method called → state changes → event added to DomainEvents list
|
|
248
|
-
2. Repository saves Aggregate
|
|
249
|
-
3. After save (in same transaction or via outbox):
|
|
250
|
-
- In-process handlers: update read models, trigger other commands
|
|
251
|
-
- Cross-context: publish to message queue
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Event Handling Guidelines
|
|
255
|
-
|
|
256
|
-
- **Same Bounded Context**: Handle synchronously (same transaction OK for read models)
|
|
257
|
-
- **Cross Bounded Context**: Handle asynchronously (eventual consistency)
|
|
258
|
-
- Event handlers should be idempotent (safe to process multiple times)
|
|
259
|
-
- **Dispatch / clear lifecycle**: clear `DomainEvents` at the Repository / Unit
|
|
260
|
-
of Work **save boundary** — the UoW clears them *after* the save succeeds and
|
|
261
|
-
the events have been dispatched (or the outbox row persisted, or dispatch
|
|
262
|
-
explicitly decided to be dropped/deferred). Never clear inside an Aggregate
|
|
263
|
-
method, and never before the save succeeds. A simple in-process dispatcher
|
|
264
|
-
(e.g. `IPublisher` / MediatR) is enough for Phase 1; an **outbox /
|
|
265
|
-
integration-event bridge is the Phase 2+ upgrade** for reliable cross-service
|
|
266
|
-
delivery. If no dispatcher is wired yet, record it as deferred tech debt — but
|
|
267
|
-
still clear on a successful save so events cannot accumulate unbounded.
|
|
268
|
-
|
|
269
|
-
## Specifications
|
|
270
|
-
|
|
271
|
-
For complex query logic that belongs to the domain:
|
|
272
|
-
|
|
273
|
-
```csharp
|
|
274
|
-
public class PendingApprovalSpec : Specification<ExpenseReport>
|
|
275
|
-
{
|
|
276
|
-
private readonly EmployeeId _approverId;
|
|
277
|
-
|
|
278
|
-
public PendingApprovalSpec(EmployeeId approverId)
|
|
279
|
-
{
|
|
280
|
-
_approverId = approverId;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
public override Expression<Func<ExpenseReport, bool>> ToExpression()
|
|
284
|
-
{
|
|
285
|
-
return report =>
|
|
286
|
-
report.Status == ReportStatus.Submitted &&
|
|
287
|
-
report.ApproverId == _approverId;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Domain Services
|
|
293
|
-
|
|
294
|
-
Use Domain Services for operations that:
|
|
295
|
-
- Involve multiple Aggregates (read-only access to the second Aggregate)
|
|
296
|
-
- Require external information (through interfaces) to make domain decisions
|
|
297
|
-
- Don't naturally belong to any single Entity
|
|
298
|
-
|
|
299
|
-
```csharp
|
|
300
|
-
// Domain Service — in Domain layer
|
|
301
|
-
public class ExpenseApprovalService
|
|
302
|
-
{
|
|
303
|
-
private readonly IApprovalPolicyRepository _policyRepo;
|
|
304
|
-
|
|
305
|
-
public ApprovalResult Evaluate(ExpenseReport report, ApprovalPolicy policy)
|
|
306
|
-
{
|
|
307
|
-
if (report.TotalAmount.Amount > policy.AutoApprovalLimit)
|
|
308
|
-
return ApprovalResult.RequiresManagerApproval;
|
|
309
|
-
|
|
310
|
-
if (policy.RestrictedCategories.Overlaps(report.Categories))
|
|
311
|
-
return ApprovalResult.RequiresComplianceReview;
|
|
312
|
-
|
|
313
|
-
return ApprovalResult.AutoApproved;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Bounded Context Relationships
|
|
319
|
-
|
|
320
|
-
If `dflow/specs/domain/context-map.md` does not exist yet, create it from `templates/context-map.md` before documenting relationships.
|
|
321
|
-
|
|
322
|
-
Document in `dflow/specs/domain/context-map.md`:
|
|
323
|
-
|
|
324
|
-
| Relationship | Pattern | Example |
|
|
325
|
-
|---|---|---|
|
|
326
|
-
| Context A calls Context B | Customer-Supplier | Expense → HR (get employee info) |
|
|
327
|
-
| Contexts share data | Shared Kernel | Currency, Money in SharedKernel/ |
|
|
328
|
-
| Context A translates B's language | Anti-Corruption Layer | Expense → External Accounting System |
|
|
329
|
-
| Fire and forget | Domain Events | Expense → Notification (report submitted) |
|
|
330
|
-
|
|
331
|
-
## Common Mistakes to Catch
|
|
332
|
-
|
|
333
|
-
1. **Anemic Domain Model** — Entities with only getters/setters, all logic in services
|
|
334
|
-
→ Move behavior INTO the Entity/Aggregate
|
|
335
|
-
|
|
336
|
-
2. **Too-large Aggregates** — Aggregate that loads entire object graph
|
|
337
|
-
→ Split into smaller Aggregates, reference by ID
|
|
338
|
-
|
|
339
|
-
3. **Business logic in Application layer** — If-else rules in command handlers
|
|
340
|
-
→ Move to Domain (Entity methods or Domain Services)
|
|
341
|
-
|
|
342
|
-
4. **Business logic in Infrastructure** — Rules in SQL queries or EF configurations
|
|
343
|
-
→ Domain defines WHAT, Infrastructure defines HOW
|
|
344
|
-
|
|
345
|
-
5. **Direct cross-Aggregate modification** — One command modifying two Aggregates
|
|
346
|
-
→ Use Domain Events for the second Aggregate
|
|
347
|
-
|
|
348
|
-
6. **Set-based invariant guarded only in memory** — A "unique" / "only one active"
|
|
349
|
-
rule enforced solely by an in-app check, with no DB unique constraint or
|
|
350
|
-
optimistic-concurrency token → two concurrent requests can both pass and break it
|
|
351
|
-
→ Back it with a DB constraint / `rowversion`; see "Set-Based / Uniqueness Invariants"
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Project: {系統名稱} — Clean Architecture + DDD
|
|
2
|
-
|
|
3
|
-
**重要:所有開發工作都必須遵循本文件定義的流程。**
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## System Context
|
|
8
|
-
|
|
9
|
-
> 技術棧、架構、業務領域、目錄結構
|
|
10
|
-
|
|
11
|
-
### Background
|
|
12
|
-
|
|
13
|
-
這是一個遵循 Clean Architecture 與 Domain-Driven Design 的新建專案;具體 stack 詳見 `dflow/specs/shared/_overview.md`。
|
|
14
|
-
採用 SDD 流程,所有開發工作必須遵循本文件定義的流程。
|
|
15
|
-
|
|
16
|
-
### Architecture (Clean Architecture)
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
Presentation → Application → Domain ← Infrastructure
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
依賴方向永遠朝內。Domain 層是核心,不依賴任何外部套件。
|
|
23
|
-
|
|
24
|
-
**各層職責**
|
|
25
|
-
|
|
26
|
-
| 層 | 職責 | 不可以做的事 |
|
|
27
|
-
|---|---|---|
|
|
28
|
-
| Domain | 業務規則、Aggregate、Value Object、Domain Event | 依賴外部套件、存取資料庫、處理 HTTP |
|
|
29
|
-
| Application | 編排領域操作、CQRS、驗證、DTO | 包含業務邏輯、直接存取資料庫 |
|
|
30
|
-
| Infrastructure | {ORM / persistence}、外部 API、檔案存取 | 包含業務邏輯 |
|
|
31
|
-
| Presentation | HTTP 端點、Request/Response | 包含業務邏輯、直接操作 Domain 物件 |
|
|
32
|
-
|
|
33
|
-
### Project Structure
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
dflow/specs/
|
|
37
|
-
├── shared/ # 專案級治理文件(由 dflow init 寫入)
|
|
38
|
-
│ ├── _overview.md # 系統概覽與架構方向
|
|
39
|
-
│ └── _conventions.md # 規格撰寫慣例
|
|
40
|
-
├── domain/
|
|
41
|
-
│ ├── glossary.md
|
|
42
|
-
│ ├── context-map.md
|
|
43
|
-
│ └── {context}/
|
|
44
|
-
│ ├── context.md
|
|
45
|
-
│ ├── models.md
|
|
46
|
-
│ ├── rules.md
|
|
47
|
-
│ └── events.md # Domain Events 目錄
|
|
48
|
-
├── features/
|
|
49
|
-
│ ├── active/ # 進行中的 feature
|
|
50
|
-
│ │ └── {SPEC-ID}-{slug}/ # 一個 feature 一個目錄
|
|
51
|
-
│ │ ├── _index.md # Feature dashboard:Goals & Scope / Phase Specs / Current BR Snapshot / Lightweight Changes / Resume Pointer
|
|
52
|
-
│ │ ├── phase-spec-YYYY-MM-DD-{slug}.md # T1 Heavy:每 phase 一份
|
|
53
|
-
│ │ └── lightweight-YYYY-MM-DD-{slug}.md # T2 Light(或 BUG-NNN-{slug}.md)
|
|
54
|
-
│ ├── completed/ # 整個 feature 目錄 git mv 到這裡
|
|
55
|
-
│ └── backlog/
|
|
56
|
-
│ # SPEC-ID 格式:SPEC-YYYYMMDD-NNN;slug 跟隨討論語言(中文/英文皆可)
|
|
57
|
-
│ # T3 無獨立檔,只在 _index.md Lightweight Changes 寫一列
|
|
58
|
-
└── architecture/
|
|
59
|
-
├── decisions/ # ADR
|
|
60
|
-
└── tech-debt.md
|
|
61
|
-
|
|
62
|
-
src/
|
|
63
|
-
├── {Project}.Domain/
|
|
64
|
-
├── {Project}.Application/
|
|
65
|
-
├── {Project}.Infrastructure/
|
|
66
|
-
└── {Project}.WebAPI/
|
|
67
|
-
|
|
68
|
-
tests/
|
|
69
|
-
├── Domain.UnitTests/
|
|
70
|
-
├── Application.UnitTests/
|
|
71
|
-
└── Integration.Tests/
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Development Workflow
|
|
77
|
-
|
|
78
|
-
> SDD 流程、Git 整合、Domain 層規範、AI 協作
|
|
79
|
-
|
|
80
|
-
### Core Principles
|
|
81
|
-
1. **Spec Before Code** — 沒有規格就不寫實作
|
|
82
|
-
2. **Domain at the Center** — 業務邏輯只存在於 Domain 層
|
|
83
|
-
3. **Ubiquitous Language** — 使用 `dflow/specs/domain/glossary.md` 中定義的術語
|
|
84
|
-
4. **One Aggregate per Transaction** — 單一操作只修改一個 Aggregate
|
|
85
|
-
5. **Dependency Inversion** — Domain 定義介面,Infrastructure 實作
|
|
86
|
-
|
|
87
|
-
### Three Ceremony Tiers
|
|
88
|
-
|
|
89
|
-
不是每次修改都要跑完整流程。AI 依下列判準選 tier:
|
|
90
|
-
|
|
91
|
-
- **T1 Heavy** — 新功能、新 phase、新 Aggregate / BC、新增 BR、新 Domain Event →
|
|
92
|
-
建獨立 phase-spec,走 `/dflow:new-feature` 或 `/dflow:new-phase`
|
|
93
|
-
- **T2 Light** — bug fix(邏輯錯誤)、UI 輸入驗證、流程分支修改(有 BR Delta
|
|
94
|
-
但無 Aggregate / 資料結構動)→ 建獨立 lightweight spec 置於 feature 目錄內
|
|
95
|
-
- **T3 Trivial** — 按鈕顏色、文案修正、typo、排版、純註解(無 BR 變動、
|
|
96
|
-
無 Domain 概念動、無資料結構動、只改 UI 表層 / 註解 / 格式化)→
|
|
97
|
-
只在 `_index.md` Lightweight Changes inline 寫一列
|
|
98
|
-
|
|
99
|
-
純 typo / 純格式化 commit(`dotnet format` / `prettier` 自動整理)**低於 T3**:
|
|
100
|
-
直接 `git commit`,不走 Dflow。
|
|
101
|
-
|
|
102
|
-
### New Feature
|
|
103
|
-
1. 建 feature 目錄 `dflow/specs/features/active/{SPEC-ID}-{slug}/`
|
|
104
|
-
2. 建 `_index.md`(feature dashboard)+ 第一份 `phase-spec-YYYY-MM-DD-{slug}.md`
|
|
105
|
-
3. 設計 Aggregate(不變條件、狀態變更方法、Domain Events)
|
|
106
|
-
4. 實作順序:Domain → Application → Infrastructure → Presentation
|
|
107
|
-
5. 撰寫測試:Domain 單元測試 → Application 測試 → 整合測試
|
|
108
|
-
|
|
109
|
-
### New Phase
|
|
110
|
-
1. 在已啟動的 active feature 上新增一份 phase-spec(含 Delta-from-prior-phases)
|
|
111
|
-
2. 更新 `_index.md` 的 Phase Specs 表 + regenerate Current BR Snapshot
|
|
112
|
-
3. 嚴格只適用於 active feature;completed 的 feature 不接受新 phase
|
|
113
|
-
|
|
114
|
-
### Modify Existing
|
|
115
|
-
1. AI 依 T1 / T2 / T3 判準分流
|
|
116
|
-
2. 若偵測到改動與 completed feature 相關,主動詢問是否為 follow-up
|
|
117
|
-
(follow-up 走新建 feature + `follow-up-of` 鏈回原 feature;不把 T2/T3
|
|
118
|
-
寫回 completed 目錄)
|
|
119
|
-
3. 確認 fix 在正確的 Clean Architecture 層
|
|
120
|
-
|
|
121
|
-
### Bug Fix
|
|
122
|
-
1. 建立輕量規格
|
|
123
|
-
2. 若 bug 不附掛既有 feature,先建最小 feature 目錄再放 lightweight-spec
|
|
124
|
-
3. 找到問題所在的層
|
|
125
|
-
4. 在正確的層修復
|
|
126
|
-
|
|
127
|
-
### Feature Closeout
|
|
128
|
-
1. 驗證 feature 目錄內所有 phase-spec `status: completed`
|
|
129
|
-
2. 把 `_index.md` Current BR Snapshot 同步到 BC 層 `rules.md` /
|
|
130
|
-
`behavior.md` / `events.md` / `context-map.md`
|
|
131
|
-
3. `git mv` 整個 feature 目錄從 `active/` 搬到 `completed/`
|
|
132
|
-
4. 產出 Integration Summary(Git-strategy-neutral;不自動 merge)
|
|
133
|
-
|
|
134
|
-
### Git Integration
|
|
135
|
-
|
|
136
|
-
> 本流程只規定 SDD 必要的最小 Git 耦合(feature branch per feature、
|
|
137
|
-
> `git mv`、commit 對應 SPEC-ID)。實際採用的分支策略(Git Flow /
|
|
138
|
-
> GitHub Flow / trunk-based / 單一 main)由專案決定,不在此強制。
|
|
139
|
-
> 若採用 Git Flow,可參考 `scaffolding/Git-principles-gitflow.md` 範本。
|
|
140
|
-
|
|
141
|
-
**分支命名**
|
|
142
|
-
```
|
|
143
|
-
feature/{SPEC-ID}-{short-description} # 新功能(SDD 必須)
|
|
144
|
-
bugfix/{BUG-ID}-{short-description} # Bug 修復(SDD 必須)
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**Commit Message**
|
|
148
|
-
```
|
|
149
|
-
[SPEC-ID] 簡述變更
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
> `/dflow:bug-fix` 不綁定任何分支策略;採 Git Flow 的專案可選擇把緊急修復
|
|
153
|
-
> 放在 `hotfix/` 分支,但這是專案決策,Dflow 不代為規定。
|
|
154
|
-
|
|
155
|
-
### Domain Layer Rules
|
|
156
|
-
|
|
157
|
-
- ❌ 不可有任何外部套件依賴(語言純粹 types)
|
|
158
|
-
- ❌ 不可有 ORM 屬性([Table], [Column] 等)
|
|
159
|
-
- ❌ 不可有序列化屬性([JsonProperty] 等)
|
|
160
|
-
- ❌ 不可有 DbContext、IConfiguration、HttpClient
|
|
161
|
-
- ✅ Entity 使用 private setter,透過方法改變狀態
|
|
162
|
-
- ✅ Value Object 使用 record,建構式驗證
|
|
163
|
-
- ✅ Aggregate Root 管理 DomainEvents 集合
|
|
164
|
-
- ✅ 其他 Aggregate 只透過 ID 引用
|
|
165
|
-
|
|
166
|
-
### AI Collaboration Notes
|
|
167
|
-
|
|
168
|
-
- 開發者提出任何需求時,先引導建立 spec 和 Aggregate 設計
|
|
169
|
-
- 確認實作順序:Domain → Application → Infrastructure → Presentation
|
|
170
|
-
- 發現業務邏輯在錯誤的層時,指出並建議搬移
|
|
171
|
-
- 每次開發循環結束時,提醒更新術語表、models.md、events.md
|
|
172
|
-
- Review 時檢查 Domain 層的純淨度(零外部依賴)
|