@eventmodelers/cli 1.0.36 → 1.0.37
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/package.json +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +12 -10
- package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/SKILL.md +2 -23
- package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/references/api-fallback.md +11 -0
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +6 -57
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/references/api-fallback.md +68 -0
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +4 -61
- package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +39 -0
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +9 -53
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +63 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +9 -319
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/references/examples.md +329 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +23 -199
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +97 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/examples.md +35 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +13 -410
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/api-fallback.md +22 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/examples.md +397 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +132 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/references/api-fallback.md +21 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +9 -236
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/references/examples.md +257 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +28 -302
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/api-fallback.md +31 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/examples.md +216 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +30 -343
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +79 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/examples.md +282 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +51 -400
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +67 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/examples.md +273 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +45 -152
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +49 -90
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +64 -137
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +74 -65
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +51 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +1 -5
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/references/api-fallback.md +10 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +19 -36
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/api-fallback.md +41 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +12 -9
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +1 -10
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +26 -332
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +77 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/examples.md +271 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +9 -294
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/references/examples.md +306 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +12 -11
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +6 -36
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +3 -110
- package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +118 -0
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +5 -25
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/references/api-fallback.md +35 -0
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +9 -44
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +51 -0
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +23 -183
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +193 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +14 -81
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +74 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +4 -45
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +44 -0
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +19 -88
- package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +91 -0
- package/stacks/modeling-kit/templates/.claude/skills/update-prompt-status/SKILL.md +1 -9
- package/stacks/modeling-kit/templates/.claude/skills/update-prompt-status/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +0 -674
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +0 -204
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
# Domain-Specific Stream
|
|
1
|
+
# Domain-Specific Stream Boundary Patterns
|
|
2
2
|
|
|
3
3
|
## Contents
|
|
4
4
|
- E-commerce patterns
|
|
5
5
|
- Banking patterns
|
|
6
6
|
- SaaS patterns
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
Each entry shows a well-scoped boundary for that domain: what identity it's anchored on, and why that identity — not a category or a time window — is the right one.
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -12,32 +13,31 @@
|
|
|
12
13
|
|
|
13
14
|
**Order Aggregate**:
|
|
14
15
|
```
|
|
15
|
-
|
|
16
|
+
Identity: orderId
|
|
16
17
|
Lifetime: 1-3 years
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Reason: Short entity lifetime, low frequency, few state changes
|
|
18
|
+
Boundary: Everything that happens to this one order
|
|
19
|
+
Reason for this boundary: the order has a clear owner (one customer,
|
|
20
|
+
one checkout) and a natural end (delivered, cancelled, refunded)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
**Shopping Cart Aggregate**:
|
|
24
24
|
```
|
|
25
|
-
|
|
26
|
-
Lifetime: 30 minutes to 2 years (varies widely)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
Identity: cartId (or customerId, if a customer has exactly one active cart)
|
|
26
|
+
Lifetime: 30 minutes to 2 years (varies widely by product)
|
|
27
|
+
Boundary: Items added/removed for this one cart
|
|
28
|
+
Watch for: an "abandoned" cart and an "active" cart are different
|
|
29
|
+
lifecycles — split into separate streams if their behavior diverges
|
|
30
|
+
(e.g. abandoned-cart recovery vs. active checkout flow)
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
**User Account Aggregate**:
|
|
34
34
|
```
|
|
35
|
-
|
|
35
|
+
Identity: userId
|
|
36
36
|
Lifetime: 5-10+ years
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
Boundary: Profile and account-level state for this one user
|
|
38
|
+
Watch for: account state, preferences, and session history are three
|
|
39
|
+
different concerns with different change frequency — split them
|
|
40
|
+
(UserProfile / UserPreferences / UserSessions) rather than bundling
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
---
|
|
@@ -46,34 +46,31 @@ Reason: Infrequent events, long lifetime, many separate streams
|
|
|
46
46
|
|
|
47
47
|
**Account Aggregate**:
|
|
48
48
|
```
|
|
49
|
-
|
|
49
|
+
Identity: accountId
|
|
50
50
|
Lifetime: 10-50+ years
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Alternative: Snapshotting might be justified for regulatory access needs
|
|
51
|
+
Boundary: Deposits, withdrawals, and fees for this one account
|
|
52
|
+
Watch for: an account spanning decades is still one identity — the
|
|
53
|
+
boundary is correct even though the stream is long-lived; don't
|
|
54
|
+
split it just because it accumulates many events over a long life
|
|
56
55
|
```
|
|
57
56
|
|
|
58
57
|
**Transaction Aggregate**:
|
|
59
58
|
```
|
|
60
|
-
|
|
59
|
+
Identity: transactionId
|
|
61
60
|
Lifetime: 1-2 months (then archived)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Reason: Tiny, immutable after completion
|
|
61
|
+
Boundary: Requested → Processing → Settled, for this one transaction
|
|
62
|
+
Reason for this boundary: a transaction is a short, self-contained
|
|
63
|
+
lifecycle — treat it as its own stream, not folded into the account
|
|
66
64
|
```
|
|
67
65
|
|
|
68
66
|
**Loan Aggregate**:
|
|
69
67
|
```
|
|
70
|
-
|
|
68
|
+
Identity: loanId
|
|
71
69
|
Lifetime: 5-30 years
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Example: ActiveLoan vs. CompletedLoan aggregates
|
|
70
|
+
Boundary: Payments, rate changes, and modifications for this one loan
|
|
71
|
+
Watch for: "active loan" and "completed loan" are different
|
|
72
|
+
lifecycles with different access patterns — consider splitting into
|
|
73
|
+
ActiveLoan vs. CompletedLoan if their consumers genuinely differ
|
|
77
74
|
```
|
|
78
75
|
|
|
79
76
|
---
|
|
@@ -82,69 +79,31 @@ Example: ActiveLoan vs. CompletedLoan aggregates
|
|
|
82
79
|
|
|
83
80
|
**Subscription Aggregate**:
|
|
84
81
|
```
|
|
85
|
-
|
|
82
|
+
Identity: subscriptionId
|
|
86
83
|
Lifetime: 1-5+ years
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Reason: Low frequency, well-defined lifecycle
|
|
84
|
+
Boundary: Created, Upgraded, Downgraded, Cancelled — for this one subscription
|
|
85
|
+
Reason for this boundary: a subscription has one clear owner and a
|
|
86
|
+
well-defined lifecycle
|
|
91
87
|
```
|
|
92
88
|
|
|
93
89
|
**User Workspace Aggregate**:
|
|
94
90
|
```
|
|
95
|
-
|
|
91
|
+
Identity: workspaceId
|
|
96
92
|
Lifetime: 2-5+ years
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
Boundary: Members added, roles changed, settings updated — for this one workspace
|
|
94
|
+
Watch for: workspace-level settings and individual member activity
|
|
95
|
+
are different concerns — keep member activity in its own stream if
|
|
96
|
+
it needs independent access patterns
|
|
101
97
|
```
|
|
102
98
|
|
|
103
99
|
**Data Collection Aggregate**:
|
|
104
100
|
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
→ If YES, snapshotting might be needed
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Implementation Checklist
|
|
119
|
-
|
|
120
|
-
Before implementing snapshotting, answer ALL of these:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
Design Questions:
|
|
124
|
-
[ ] Does this aggregate have a single business identity?
|
|
125
|
-
[ ] Can I split this into smaller aggregates?
|
|
126
|
-
[ ] Are there natural lifecycle phases (archived vs. active)?
|
|
127
|
-
[ ] Is event granularity appropriate (not too fine)?
|
|
128
|
-
|
|
129
|
-
Performance Questions:
|
|
130
|
-
[ ] Have I measured replay latency?
|
|
131
|
-
[ ] Does latency exceed acceptable threshold?
|
|
132
|
-
[ ] Is the problem snaphotting will solve?
|
|
133
|
-
[ ] Or is it a design problem?
|
|
134
|
-
|
|
135
|
-
Cost-Benefit Questions:
|
|
136
|
-
[ ] How many writes per second?
|
|
137
|
-
[ ] How many reads per second?
|
|
138
|
-
[ ] What's the read latency requirement (SLA)?
|
|
139
|
-
[ ] Is snapshotting complexity worth the benefit?
|
|
140
|
-
|
|
141
|
-
Operational Questions:
|
|
142
|
-
[ ] How will I version snapshots?
|
|
143
|
-
[ ] How will I test snapshot recovery?
|
|
144
|
-
[ ] How will I monitor snapshot health?
|
|
145
|
-
[ ] Can I implement this given current skills?
|
|
101
|
+
Identity: depends on what's actually being tracked as one thing
|
|
102
|
+
Watch for: this is the domain most likely to hide a Pattern 3
|
|
103
|
+
("Collection") anti-pattern — ask explicitly: "are all these data
|
|
104
|
+
points about the same business entity?"
|
|
105
|
+
→ If NO, the boundary is wrong: split by whatever entity each
|
|
106
|
+
data point actually belongs to.
|
|
107
|
+
→ If YES, the boundary is fine even if the stream accumulates a
|
|
108
|
+
lot of events — that's a volume question, not a boundary one.
|
|
146
109
|
```
|
|
147
|
-
|
|
148
|
-
**If ANY question suggests redesign is better**: Redesign first, snapshot never.
|
|
149
|
-
|
|
150
|
-
**If ALL questions support snapshotting**: Proceed with implementation.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Stream Boundary Design Patterns
|
|
2
2
|
|
|
3
3
|
## Contents
|
|
4
4
|
- Aggregate Boundary Design Patterns (5 patterns with examples)
|
|
5
|
-
- Stream
|
|
6
|
-
- Red Flags:
|
|
7
|
-
- Tips for
|
|
5
|
+
- Stream Boundary Decision Tree
|
|
6
|
+
- Red Flags: Boundary Is Wrong
|
|
7
|
+
- Tips for Finding the Right Boundary
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
### Pattern 1: Single Entity (Most Common)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
CORRECT: One aggregate per entity
|
|
16
16
|
```
|
|
17
17
|
Aggregate: Order
|
|
18
18
|
Root Identity: orderId (e.g., 'order-123')
|
|
@@ -28,9 +28,6 @@ Events in stream:
|
|
|
28
28
|
6. OrderShipped (2024-01-20)
|
|
29
29
|
7. OrderDelivered (2024-01-25)
|
|
30
30
|
|
|
31
|
-
Stream Length: 7 events
|
|
32
|
-
Snapshotting: NOT NEEDED
|
|
33
|
-
|
|
34
31
|
Identity Principle: orderId is the natural business key
|
|
35
32
|
Boundary: Everything about THIS specific order, nothing else
|
|
36
33
|
Consistency: Only one order being modified at a time
|
|
@@ -40,7 +37,7 @@ Consistency: Only one order being modified at a time
|
|
|
40
37
|
|
|
41
38
|
### Pattern 2: Composite Entity (Proper Composition)
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
CORRECT: Aggregate contains related child entities
|
|
44
41
|
```
|
|
45
42
|
Aggregate: Order
|
|
46
43
|
Root Identity: orderId (e.g., 'order-456')
|
|
@@ -66,9 +63,6 @@ Events in stream:
|
|
|
66
63
|
6. PaymentProcessed (authorization complete)
|
|
67
64
|
7. OrderShipped (tracking 123456)
|
|
68
65
|
|
|
69
|
-
Stream Length: 7 events
|
|
70
|
-
Snapshotting: NOT NEEDED (well under 1000)
|
|
71
|
-
|
|
72
66
|
Pattern: Small, bounded number of children per parent
|
|
73
67
|
Lifetime: Parent and all children created/destroyed together
|
|
74
68
|
Consistency: All modified as a unit (can't ship without payment, etc.)
|
|
@@ -78,7 +72,7 @@ Consistency: All modified as a unit (can't ship without payment, etc.)
|
|
|
78
72
|
|
|
79
73
|
### Pattern 3: Collection (ANTI-PATTERN - DO NOT USE)
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
WRONG: Treating a collection as an aggregate
|
|
82
76
|
```
|
|
83
77
|
Bad Aggregate: AllOrders
|
|
84
78
|
Root Identity: "all-orders-collection" (artificial, meaningless)
|
|
@@ -91,19 +85,11 @@ Events:
|
|
|
91
85
|
4. OrderCreated (customer-003, order-004)
|
|
92
86
|
... (continues forever, unbounded)
|
|
93
87
|
|
|
94
|
-
Month 1: 50,000 events
|
|
95
|
-
Year 1: 600,000 events
|
|
96
|
-
Year 5: 3,000,000 events
|
|
97
|
-
|
|
98
|
-
Stream Length: 1,000,000+ events
|
|
99
|
-
Snapshotting: Doesn't help - design is fundamentally wrong
|
|
100
|
-
|
|
101
88
|
Problems with this approach:
|
|
102
89
|
- No single business identity (it's a collection, not an entity)
|
|
103
|
-
- Stream grows unbounded
|
|
104
|
-
- Snapshotting won't fix it (snapshot is also 1M+ events)
|
|
90
|
+
- Stream grows unbounded — no natural end to its lifetime
|
|
105
91
|
- Can't split or scale
|
|
106
|
-
- Every write goes to same stream (contention)
|
|
92
|
+
- Every write goes to the same stream (contention)
|
|
107
93
|
|
|
108
94
|
Solution: Use a projection/read model query instead, not an aggregate
|
|
109
95
|
- Query: "GetAllOrdersByCustomer(customer-id)"
|
|
@@ -115,7 +101,7 @@ Solution: Use a projection/read model query instead, not an aggregate
|
|
|
115
101
|
|
|
116
102
|
### Pattern 4: Event Log (ANTI-PATTERN - DO NOT USE)
|
|
117
103
|
|
|
118
|
-
|
|
104
|
+
WRONG: Using an aggregate as an event log
|
|
119
105
|
```
|
|
120
106
|
Bad Aggregate: SystemLog
|
|
121
107
|
Root Identity: "system-log" (meaningless placeholder)
|
|
@@ -130,37 +116,30 @@ Events:
|
|
|
130
116
|
6. UserLoggedIn (user-223)
|
|
131
117
|
... (grows indefinitely, no pattern)
|
|
132
118
|
|
|
133
|
-
Per Day: 100,000+ events
|
|
134
|
-
Per Year: 36,500,000+ events
|
|
135
|
-
|
|
136
|
-
Stream Length: 10,000,000+ events
|
|
137
|
-
Snapshotting: Impossible - design is fundamentally broken
|
|
138
|
-
|
|
139
119
|
Problems with this approach:
|
|
140
120
|
- No business identity (log of everything)
|
|
141
121
|
- Events unrelated to each other (mixing user, order, payment, inventory)
|
|
142
122
|
- No consistency boundary (user login != order creation)
|
|
143
123
|
- Can't answer "what's the state of X?" (too mixed)
|
|
144
|
-
- Contention: every subsystem writing to same stream
|
|
124
|
+
- Contention: every subsystem writing to the same stream
|
|
145
125
|
- Can't replay meaningfully (mixed concerns)
|
|
146
126
|
|
|
147
|
-
Solution: Use separate event
|
|
127
|
+
Solution: Use separate event streams per business entity
|
|
148
128
|
- Keep dedicated event streams: Order, Payment, Inventory, User
|
|
149
|
-
- Use time-series
|
|
150
|
-
- Query system logs separately from domain events
|
|
129
|
+
- Use a time-series database for metrics/logs, not a domain stream
|
|
151
130
|
```
|
|
152
131
|
|
|
153
132
|
---
|
|
154
133
|
|
|
155
134
|
### Pattern 5: Historical Aggregate (GOOD - When Needed)
|
|
156
135
|
|
|
157
|
-
|
|
136
|
+
CORRECT: Keep historical data for audit/compliance as its own boundary
|
|
158
137
|
```
|
|
159
138
|
Aggregate: ArchivedOrder
|
|
160
139
|
Root Identity: archivedOrderId (e.g., 'archived-order-001')
|
|
161
140
|
Purpose: Regulatory compliance (7-year retention)
|
|
162
141
|
|
|
163
|
-
Contains:
|
|
142
|
+
Contains: A record + audit trail of an order
|
|
164
143
|
Events:
|
|
165
144
|
1. OrderArchived (original order-123 on 2023-12-31)
|
|
166
145
|
- reason: compliance_retention
|
|
@@ -177,153 +156,101 @@ Events:
|
|
|
177
156
|
... (additional audit entries over time)
|
|
178
157
|
|
|
179
158
|
Lifetime: 7 years (regulatory requirement)
|
|
180
|
-
Stream Length: 500-2000 events (audit entries added slowly)
|
|
181
|
-
Snapshotting: Not needed (historical, not active)
|
|
182
159
|
|
|
183
160
|
Key architectural principles:
|
|
184
|
-
- Completely separate from active Order aggregate
|
|
161
|
+
- Completely separate from the active Order aggregate
|
|
185
162
|
- Active Order is for current business operations
|
|
186
|
-
- Archived Order is immutable historical record
|
|
187
|
-
- Different access patterns, different
|
|
163
|
+
- Archived Order is an immutable historical record
|
|
164
|
+
- Different access patterns, different lifecycles
|
|
188
165
|
```
|
|
189
166
|
|
|
190
167
|
---
|
|
191
168
|
|
|
192
|
-
## Stream
|
|
169
|
+
## Stream Boundary Decision Tree
|
|
193
170
|
|
|
194
|
-
Use this to decide
|
|
171
|
+
Use this to decide whether a stream is bounded around the right business identity:
|
|
195
172
|
|
|
196
173
|
```
|
|
197
174
|
Does your stream have a natural business identity?
|
|
198
175
|
NO → This is not an aggregate, it's a log/report
|
|
199
|
-
SOLUTION: Use read model/projection, not aggregate
|
|
176
|
+
SOLUTION: Use a read model/projection, not an aggregate
|
|
200
177
|
|
|
201
|
-
YES →
|
|
202
|
-
|
|
203
|
-
< 100 events
|
|
204
|
-
PERFECT: No optimization needed
|
|
178
|
+
YES → Does every event in the stream belong to that one entity's lifecycle?
|
|
205
179
|
|
|
206
|
-
|
|
207
|
-
Is it growing because of high frequency?
|
|
208
|
-
NO → GOOD: Probably well-designed
|
|
209
|
-
YES → MONITOR: Watch for growth
|
|
210
|
-
|
|
211
|
-
Does each event represent a meaningful state change?
|
|
212
|
-
YES → GOOD: Healthy stream
|
|
213
|
-
NO → REDESIGN: Too granular events
|
|
180
|
+
YES → GOOD: Boundary is correctly scoped to one identity
|
|
214
181
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
182
|
+
NO → The boundary is too wide — it's absorbing events that
|
|
183
|
+
belong to a different entity or a different concern
|
|
184
|
+
REDESIGN: Split by the entity each event actually concerns
|
|
218
185
|
Examples: User → UserProfile + UserSessions
|
|
219
|
-
Order → Order + OrderLineItems
|
|
220
|
-
|
|
221
|
-
NO → Is read frequency high (> 10/sec)?
|
|
222
|
-
YES → Consider snapshotting at 5000
|
|
223
|
-
NO → ACCEPTABLE: Leave as-is
|
|
224
|
-
|
|
225
|
-
Is latency critical (< 100ms)?
|
|
226
|
-
YES → MONITOR: Measure replay time
|
|
227
|
-
NO → ACCEPTABLE: No snapshotting needed
|
|
228
|
-
|
|
229
|
-
5000-10000 events
|
|
230
|
-
This is a design problem → REDESIGN
|
|
231
|
-
OR business justifies complexity → Snapshot at 5000
|
|
232
|
-
|
|
233
|
-
Questions before snapshotting:
|
|
234
|
-
Can I split aggregate? (usually YES)
|
|
235
|
-
Can I reduce event granularity? (sometimes)
|
|
236
|
-
Am I using a read model for this aggregate? (maybe not)
|
|
237
|
-
If all NO → Then snapshot is justified
|
|
238
|
-
|
|
239
|
-
> 10000 events
|
|
240
|
-
CRITICAL: Redesign required
|
|
241
|
-
This is NOT a properly designed aggregate
|
|
242
|
-
Snapshotting won't save you
|
|
243
|
-
Root cause: Aggregate boundary is wrong
|
|
186
|
+
Order → Order + OrderLineItems (if line items
|
|
187
|
+
have their own independent lifecycle)
|
|
244
188
|
```
|
|
245
189
|
|
|
246
190
|
---
|
|
247
191
|
|
|
248
|
-
## Red Flags:
|
|
192
|
+
## Red Flags: Boundary Is Wrong
|
|
249
193
|
|
|
250
|
-
If your stream exhibits ANY of these,
|
|
194
|
+
If your stream exhibits ANY of these, the fix is a narrower or different identity — not a technical workaround:
|
|
251
195
|
|
|
252
196
|
```
|
|
253
|
-
|
|
254
|
-
Cause:
|
|
255
|
-
Solution:
|
|
256
|
-
Example: "
|
|
197
|
+
Red Flag 1: Stream growing continuously with no natural end
|
|
198
|
+
Cause: The stream identity spans an unbounded population, not one entity
|
|
199
|
+
Solution: Re-scope to a single business entity's lifecycle
|
|
200
|
+
Example: "AllOrders" stream → "Order" per customer order
|
|
257
201
|
|
|
258
|
-
|
|
259
|
-
Cause: Treating log as aggregate
|
|
260
|
-
Solution: Use read model/query instead of aggregate
|
|
261
|
-
Example: "SystemMetricRecorded"
|
|
202
|
+
Red Flag 2: Events with no shared business meaning
|
|
203
|
+
Cause: Treating a log as an aggregate
|
|
204
|
+
Solution: Use a read model/query instead of an aggregate
|
|
205
|
+
Example: "SystemMetricRecorded" → use a metrics/observability system
|
|
262
206
|
|
|
263
|
-
|
|
207
|
+
Red Flag 3: Stream contains unrelated entities
|
|
264
208
|
Cause: Aggregate boundary is wrong
|
|
265
209
|
Solution: Split into separate aggregates
|
|
266
|
-
Example: "AllOrders"
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
Cause:
|
|
270
|
-
Solution:
|
|
271
|
-
Example:
|
|
272
|
-
|
|
273
|
-
Red Flag 5: Can't explain what business question the stream answers
|
|
274
|
-
Cause: Not a real aggregate
|
|
275
|
-
Solution: Convert to read model/projection
|
|
276
|
-
Example: "SystemEvents" → Query specific streams
|
|
277
|
-
|
|
278
|
-
Red Flag 6: Stream length doubles every 6 months
|
|
279
|
-
Cause: Exponential growth pattern
|
|
280
|
-
Solution: Likely aggregate boundary issue
|
|
281
|
-
Example: Split by time period: 2024-Orders vs. 2025-Orders
|
|
210
|
+
Example: "AllOrders" → "Order" per customer
|
|
211
|
+
|
|
212
|
+
Red Flag 4: Can't explain what single business question the stream answers
|
|
213
|
+
Cause: Not a real aggregate — probably a collection or log
|
|
214
|
+
Solution: Convert to a read model/projection
|
|
215
|
+
Example: "SystemEvents" → query specific per-entity streams instead
|
|
282
216
|
```
|
|
283
217
|
|
|
284
218
|
---
|
|
285
219
|
|
|
286
|
-
## Tips for
|
|
220
|
+
## Tips for Finding the Right Boundary
|
|
287
221
|
|
|
288
|
-
### 1.
|
|
222
|
+
### 1. Anchor on a Single Business Identity
|
|
289
223
|
```
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
224
|
+
Every stream should answer: "the history of exactly which entity?"
|
|
225
|
+
If the answer is "a category of things" or "everything," the
|
|
226
|
+
boundary is wrong — it's describing a collection, not an entity.
|
|
293
227
|
```
|
|
294
228
|
|
|
295
229
|
### 2. Understand Event Granularity
|
|
296
230
|
```
|
|
297
|
-
|
|
298
|
-
|
|
231
|
+
RIGHT: One event per meaningful state change
|
|
232
|
+
WRONG: Multiple events per semantic operation
|
|
299
233
|
Example: "UserUpdatedProfile" (1 event)
|
|
300
234
|
NOT: "FirstNameChanged", "LastNameChanged", ... (N events)
|
|
301
235
|
```
|
|
302
236
|
|
|
303
|
-
### 3. Split When
|
|
237
|
+
### 3. Split When the Identity Is Actually Two Identities
|
|
304
238
|
```
|
|
305
|
-
|
|
306
|
-
Order (per order)
|
|
307
|
-
OrderLine (per line item)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
UserProfile (personal info)
|
|
311
|
-
UserPreferences (settings)
|
|
312
|
-
UserSessions (login history)
|
|
239
|
+
AllOrders (growing unbounded, no single identity)
|
|
240
|
+
→ Order (per order)
|
|
241
|
+
→ OrderLine (per line item, if it has its own independent lifecycle)
|
|
242
|
+
|
|
243
|
+
UserAccount (everything about a user, several unrelated concerns)
|
|
244
|
+
→ UserProfile (personal info)
|
|
245
|
+
→ UserPreferences (settings)
|
|
246
|
+
→ UserSessions (login history)
|
|
313
247
|
```
|
|
314
248
|
|
|
315
|
-
### 4.
|
|
249
|
+
### 4. Separate Active From Historical Concerns
|
|
316
250
|
```
|
|
317
|
-
|
|
318
|
-
|
|
251
|
+
Keeping everything in one aggregate forever conflates two different
|
|
252
|
+
lifecycles: the entity while it's active, and its record afterward.
|
|
319
253
|
Example:
|
|
320
254
|
- ActiveSubscription (current state)
|
|
321
255
|
- ArchivedSubscription (after cancelled)
|
|
322
256
|
```
|
|
323
|
-
|
|
324
|
-
### 5. Measure Before Optimizing
|
|
325
|
-
```
|
|
326
|
-
Assume snapshotting is needed
|
|
327
|
-
Measure replay latency first
|
|
328
|
-
Only snapshot if measurement justifies it
|
|
329
|
-
```
|