mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 17:49:57 +01:00
348 lines
11 KiB
Markdown
348 lines
11 KiB
Markdown
# DDD Modeling Workflow
|
|
|
|
This document provides detailed instructions for each phase of the DDD modeling workflow.
|
|
|
|
## Phase 1: Domain Discovery
|
|
|
|
### Step 1.1: Gather Domain Information
|
|
|
|
Use AskUserQuestion to understand the domain:
|
|
|
|
```json
|
|
{
|
|
"question": "What domain or subdomain are you modeling?",
|
|
"header": "Domain",
|
|
"multiSelect": false,
|
|
"options": [
|
|
{"label": "New domain", "description": "Starting from scratch with a new business domain"},
|
|
{"label": "Existing domain", "description": "Refactoring or extending an existing domain"},
|
|
{"label": "Subdomain extraction", "description": "Extracting a bounded context from a monolith"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 1.2: Classify Subdomain Type
|
|
|
|
```json
|
|
{
|
|
"question": "What type of subdomain is this?",
|
|
"header": "Type",
|
|
"multiSelect": false,
|
|
"options": [
|
|
{"label": "Core (Recommended)", "description": "Competitive advantage, complex business logic, high DDD investment"},
|
|
{"label": "Supporting", "description": "Necessary for Core, moderate complexity, simplified DDD"},
|
|
{"label": "Generic", "description": "Commodity functionality, low complexity, CRUD is fine"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 1.3: Determine DDD Investment Level
|
|
|
|
Based on subdomain type:
|
|
|
|
| Subdomain Type | DDD Investment | Patterns to Use |
|
|
|----------------|----------------|-----------------|
|
|
| Core | Full | Aggregates, Domain Events, Domain Services, CQRS, Event Sourcing (optional) |
|
|
| Supporting | Simplified | Aggregates, basic Value Objects, simple Domain Services |
|
|
| Generic | Minimal | CRUD, Transaction Script, Active Record |
|
|
|
|
### Step 1.4: Identify Business Processes
|
|
|
|
Ask about key business processes:
|
|
|
|
```json
|
|
{
|
|
"question": "What are the main business processes in this domain?",
|
|
"header": "Processes",
|
|
"multiSelect": true,
|
|
"options": [
|
|
{"label": "Create/Register", "description": "Creating new domain entities"},
|
|
{"label": "Update/Modify", "description": "Changing existing entities"},
|
|
{"label": "Workflow/State machine", "description": "Multi-step processes with state transitions"},
|
|
{"label": "Calculations", "description": "Complex business calculations or rules"}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Bounded Contexts
|
|
|
|
### Step 2.1: List Domain Concepts
|
|
|
|
Ask the user to list key concepts:
|
|
|
|
"List the main concepts/nouns in your domain. For example: Account, Transaction, Customer, Payment, etc."
|
|
|
|
### Step 2.2: Group Concepts into Bounded Contexts
|
|
|
|
Look for:
|
|
- Concepts that share the same ubiquitous language
|
|
- Concepts that change together
|
|
- Concepts with the same lifecycle
|
|
- Natural boundaries (teams, deployability)
|
|
|
|
### Step 2.3: Propose BC Boundaries
|
|
|
|
Present a Context Map diagram:
|
|
|
|
```
|
|
Example Context Map:
|
|
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ CORE DOMAIN │
|
|
│ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Accounts │─────────>│ Transfers │ │
|
|
│ │ │ Customer │ │ │
|
|
│ │ - Account │ Supplier│ - Transfer │ │
|
|
│ │ - Balance │ │ - Payment │ │
|
|
│ └─────────────┘ └─────────────┘ │
|
|
│ │ │ │
|
|
│ │ Conformist │ Partnership │
|
|
│ v v │
|
|
│ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Fees │ │ Loyalty │ │
|
|
│ │ (Supporting)│ │ (Core) │ │
|
|
│ └─────────────┘ └─────────────┘ │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Step 2.4: Define Ubiquitous Language
|
|
|
|
For each BC, create a glossary:
|
|
|
|
```markdown
|
|
## Accounts BC - Ubiquitous Language
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| Account | A financial account owned by a customer |
|
|
| Balance | Current amount of money in the account |
|
|
| Holder | Person or entity that owns the account |
|
|
| Freeze | Temporarily block all operations on account |
|
|
```
|
|
|
|
### Step 2.5: Map Context Relationships
|
|
|
|
Define relationships between BCs:
|
|
|
|
- **Partnership**: Teams cooperate, shared evolution
|
|
- **Customer-Supplier**: Upstream provides, downstream consumes
|
|
- **Conformist**: Downstream adopts upstream model as-is
|
|
- **Anti-corruption Layer**: Downstream translates upstream model
|
|
- **Open Host Service**: Upstream provides well-defined protocol
|
|
- **Published Language**: Shared language (XML schema, Protobuf)
|
|
|
|
---
|
|
|
|
## Phase 3: Tactical Modeling
|
|
|
|
### Step 3.1: Identify Entities
|
|
|
|
```json
|
|
{
|
|
"question": "Which concepts have a unique identity that persists over time?",
|
|
"header": "Entities",
|
|
"multiSelect": true,
|
|
"options": [
|
|
{"label": "User/Customer", "description": "Has ID, identity matters even if attributes change"},
|
|
{"label": "Order/Transaction", "description": "Tracked by ID throughout lifecycle"},
|
|
{"label": "Account/Wallet", "description": "Unique identifier, state changes over time"},
|
|
{"label": "Other (specify)", "description": "I'll describe other entities"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 3.2: Identify Value Objects
|
|
|
|
```json
|
|
{
|
|
"question": "Which concepts are defined purely by their values (no identity)?",
|
|
"header": "Value Objects",
|
|
"multiSelect": true,
|
|
"options": [
|
|
{"label": "Money/Amount", "description": "$100 = $100, no unique identity"},
|
|
{"label": "Address/Location", "description": "Same address values = same address"},
|
|
{"label": "DateRange/Period", "description": "Defined by start and end dates"},
|
|
{"label": "Email/Phone", "description": "Value-based, immutable"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 3.3: Identify Aggregates
|
|
|
|
Decision questions:
|
|
1. "What entities must always be consistent together?"
|
|
2. "What is the smallest unit that must be loaded together?"
|
|
3. "What defines a transaction boundary?"
|
|
|
|
```json
|
|
{
|
|
"question": "Which entity should be the Aggregate Root (entry point)?",
|
|
"header": "Aggregate Root",
|
|
"multiSelect": false,
|
|
"options": [
|
|
{"label": "Account", "description": "Controls Balance, Transactions within it"},
|
|
{"label": "Order", "description": "Controls OrderLines, ShippingInfo"},
|
|
{"label": "Customer", "description": "Controls Addresses, Preferences"},
|
|
{"label": "Other", "description": "Different aggregate root"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 3.4: Define Aggregate Boundaries
|
|
|
|
For each aggregate, determine:
|
|
- **Root Entity**: The entry point (only public access)
|
|
- **Child Entities**: Internal entities (private, accessed via root)
|
|
- **Value Objects**: Immutable data within aggregate
|
|
- **Invariants**: Rules that must always hold
|
|
|
|
Example:
|
|
```
|
|
Account Aggregate
|
|
├── Account (Root)
|
|
│ ├── AccountID (VO)
|
|
│ ├── Balance (VO)
|
|
│ ├── Status (VO/Enum)
|
|
│ └── Holders[] (Entity)
|
|
│ ├── HolderID (VO)
|
|
│ └── Role (VO)
|
|
└── Invariants:
|
|
- Balance >= 0 (for standard accounts)
|
|
- At least one holder with OWNER role
|
|
- Cannot debit frozen account
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 4: Invariants
|
|
|
|
### Step 4.1: Gather Business Rules
|
|
|
|
```json
|
|
{
|
|
"question": "What business rules must ALWAYS be true for this aggregate?",
|
|
"header": "Rules",
|
|
"multiSelect": true,
|
|
"options": [
|
|
{"label": "Non-negative values", "description": "Balance, quantity, amount >= 0"},
|
|
{"label": "Required relationships", "description": "Must have at least one X"},
|
|
{"label": "State constraints", "description": "Cannot do Y when in state Z"},
|
|
{"label": "Consistency rules", "description": "Sum of parts equals total"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 4.2: Formalize Invariants
|
|
|
|
Convert business rules to formal invariants:
|
|
|
|
```go
|
|
// Invariant: Account balance cannot be negative for standard accounts
|
|
// Invariant: Account must have at least one holder with OWNER role
|
|
// Invariant: Frozen account cannot process debit operations
|
|
// Invariant: Transfer amount must be positive
|
|
// Invariant: Source and destination accounts must be different
|
|
```
|
|
|
|
### Step 4.3: Map Invariants to Enforcement Points
|
|
|
|
| Invariant | Enforcement Point |
|
|
|-----------|-------------------|
|
|
| Balance >= 0 | `Account.Debit()` method |
|
|
| At least one owner | `Account` constructor, `RemoveHolder()` |
|
|
| No debit when frozen | `Account.Debit()` method |
|
|
| Positive amount | `Money` constructor |
|
|
|
|
---
|
|
|
|
## Phase 5: Code Generation
|
|
|
|
### Step 5.1: Create Folder Structure
|
|
|
|
Use `templates/folder-structure.md` to create:
|
|
|
|
```
|
|
internal/
|
|
├── domain/
|
|
│ └── <bc>/
|
|
│ ├── aggregate.go # Aggregate roots
|
|
│ ├── entity.go # Child entities
|
|
│ ├── value_objects.go # Value objects
|
|
│ ├── repository.go # Repository interfaces
|
|
│ ├── errors.go # Domain errors
|
|
│ └── events.go # Domain events (optional)
|
|
├── application/
|
|
│ └── <bc>/
|
|
│ ├── service.go # Application services / Use cases
|
|
│ └── dto.go # Data transfer objects
|
|
└── infrastructure/
|
|
└── <bc>/
|
|
├── postgres_repository.go
|
|
└── memory_repository.go
|
|
```
|
|
|
|
### Step 5.2: Generate Domain Code
|
|
|
|
For each aggregate, use templates:
|
|
1. `templates/aggregate.go.md` - Generate aggregate root
|
|
2. `templates/entity.go.md` - Generate child entities
|
|
3. `templates/value-object.go.md` - Generate value objects
|
|
4. `templates/repository.go.md` - Generate repository interface
|
|
|
|
### Step 5.3: Apply Uber Go Style Guide
|
|
|
|
- Use pointer receivers for Aggregates/Entities
|
|
- Use value receivers for Value Objects
|
|
- Add compile-time interface checks
|
|
- Create domain-specific error types
|
|
- Use constructor functions with validation
|
|
|
|
---
|
|
|
|
## Phase 6: Validation
|
|
|
|
### Step 6.1: Run DDD Checklist
|
|
|
|
Read `rules/ddd-rules.md` and check each rule:
|
|
|
|
```markdown
|
|
### Aggregate Validation
|
|
- [ ] Aggregate Root is the only public entry point
|
|
- [ ] Child entities are not directly accessible
|
|
- [ ] All changes go through Aggregate Root methods
|
|
- [ ] Invariants checked in constructor
|
|
- [ ] Invariants checked in mutation methods
|
|
- [ ] No direct references to other Aggregates
|
|
- [ ] References to other Aggregates use ID only
|
|
```
|
|
|
|
### Step 6.2: Output Validation Report
|
|
|
|
Generate a validation report:
|
|
|
|
```markdown
|
|
## DDD Validation Report
|
|
|
|
### ✅ Passed
|
|
- Aggregate boundaries are clear
|
|
- Value Objects are immutable
|
|
- Repository interfaces in domain layer
|
|
|
|
### ⚠️ Warnings
|
|
- Consider extracting X into separate Value Object
|
|
- Y method might be better as Domain Service
|
|
|
|
### ❌ Issues
|
|
- Direct reference to other Aggregate (should use ID)
|
|
- Invariant not enforced in Z method
|
|
```
|
|
|
|
### Step 6.3: Suggest Improvements
|
|
|
|
Based on validation, suggest:
|
|
- Missing Value Objects
|
|
- Potential Domain Events
|
|
- Domain Services that might be needed
|
|
- Possible CQRS opportunities
|