@eventmodelers/cli 0.0.1
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/README.md +175 -0
- package/cli.js +676 -0
- package/package.json +35 -0
- package/shared/build-kit/code-export.mjs +560 -0
- package/shared/build-kit/lib/ollama-agent.js +147 -0
- package/shared/build-kit/package.json +11 -0
- package/shared/build-kit/ralph-ollama.js +39 -0
- package/shared/build-kit/realtime-agent.js +18 -0
- package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +407 -0
- package/stacks/axon/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +114 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +496 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/axon-workflow-api.md +279 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/feature-flag-patterns.md +114 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +396 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/axon-test-fixture-patterns.md +162 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +56 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +115 -0
- package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +282 -0
- package/stacks/axon/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/axon/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +611 -0
- package/stacks/axon/templates/.claude/skills/load-slice/SKILL.md +141 -0
- package/stacks/axon/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
- package/stacks/axon/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/axon/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/axon/templates/build-kit/lib/backend-prompt.md +158 -0
- package/stacks/axon/templates/build-kit/lib/prompt.md +126 -0
- package/stacks/axon/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/axon/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/axon/templates/build-kit/ralph.sh +98 -0
- package/stacks/axon/templates/root/.env.example +5 -0
- package/stacks/axon/templates/root/CLAUDE.md +60 -0
- package/stacks/axon/templates/root/README.md +44 -0
- package/stacks/axon/templates/root/docker-compose.yml +64 -0
- package/stacks/axon/templates/root/mvnw +259 -0
- package/stacks/axon/templates/root/mvnw.cmd +149 -0
- package/stacks/axon/templates/root/pom.xml +125 -0
- package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/QuickstartApplication.java +128 -0
- package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/config/WebConfig.java +155 -0
- package/stacks/axon/templates/root/src/main/resources/application.properties +2 -0
- package/stacks/axon/templates/root/src/main/resources/static/bg-body.png +0 -0
- package/stacks/axon/templates/root/src/main/resources/static/bg-header.png +0 -0
- package/stacks/axon/templates/root/src/main/resources/static/index.html +596 -0
- package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +251 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +99 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +115 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +163 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +234 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +120 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +166 -0
- package/stacks/cratis-csharp/templates/.claude/skills/connect/SKILL.md +169 -0
- package/stacks/cratis-csharp/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +609 -0
- package/stacks/cratis-csharp/templates/.claude/skills/load-slice/SKILL.md +141 -0
- package/stacks/cratis-csharp/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +58 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +129 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +124 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/ralph.js +302 -0
- package/stacks/cratis-csharp/templates/build-kit/ralph-claude.js +37 -0
- package/stacks/cratis-csharp/templates/build-kit/ralph.sh +98 -0
- package/stacks/cratis-csharp/templates/root/.frontend/index.css +29 -0
- package/stacks/cratis-csharp/templates/root/.frontend/index.html +17 -0
- package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +18 -0
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +42 -0
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +11 -0
- package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +56 -0
- package/stacks/cratis-csharp/templates/root/App.tsx +23 -0
- package/stacks/cratis-csharp/templates/root/CLAUDE.md +62 -0
- package/stacks/cratis-csharp/templates/root/CratisApp.csproj +25 -0
- package/stacks/cratis-csharp/templates/root/CratisApp.sln +18 -0
- package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +3 -0
- package/stacks/cratis-csharp/templates/root/Home.tsx +102 -0
- package/stacks/cratis-csharp/templates/root/Program.cs +26 -0
- package/stacks/cratis-csharp/templates/root/README.md +192 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +47 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +11 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +12 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +17 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +51 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +18 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +27 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +22 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +3 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/appsettings.Development.json +9 -0
- package/stacks/cratis-csharp/templates/root/appsettings.json +26 -0
- package/stacks/cratis-csharp/templates/root/docker-compose.yml +23 -0
- package/stacks/cratis-csharp/templates/root/package.json +33 -0
- package/stacks/cratis-csharp/templates/root/tsconfig.json +3 -0
- package/stacks/modeling-kit/templates/.claude/settings.local.json +13 -0
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +215 -0
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +176 -0
- package/stacks/modeling-kit/templates/.claude/skills/connect/SKILL.md +182 -0
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +383 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +465 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +587 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/facilitating-event-modeling-workshops.md +626 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +542 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +497 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +783 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +604 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +699 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +674 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +231 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +150 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +329 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +204 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +352 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/project-planning-with-event-modeling.md +341 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +153 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +136 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +78 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +85 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +655 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +479 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +234 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +353 -0
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +200 -0
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +94 -0
- package/stacks/modeling-kit/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +441 -0
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +290 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +212 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +123 -0
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +353 -0
- package/stacks/modeling-kit/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +134 -0
- package/stacks/modeling-kit/templates/kit/lib/agent.sh +20 -0
- package/stacks/modeling-kit/templates/kit/lib/ollama-agent.js +147 -0
- package/stacks/modeling-kit/templates/kit/lib/ralph.js +286 -0
- package/stacks/modeling-kit/templates/kit/package.json +12 -0
- package/stacks/modeling-kit/templates/kit/ralph-claude.js +50 -0
- package/stacks/modeling-kit/templates/kit/ralph-ollama.js +38 -0
- package/stacks/modeling-kit/templates/kit/ralph.sh +61 -0
- package/stacks/modeling-kit/templates/kit/realtime-agent.js +18 -0
- package/stacks/modeling-kit/templates/root/CLAUDE.md +56 -0
- package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +266 -0
- package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +344 -0
- package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +412 -0
- package/stacks/node/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/node/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
- package/stacks/node/templates/.claude/skills/load-slice/SKILL.md +143 -0
- package/stacks/node/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/node/templates/build-kit/README.md +86 -0
- package/stacks/node/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/node/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/node/templates/build-kit/lib/backend-prompt.md +152 -0
- package/stacks/node/templates/build-kit/lib/prompt.md +122 -0
- package/stacks/node/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/node/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/node/templates/build-kit/ralph.sh +98 -0
- package/stacks/node/templates/root/.env.example +15 -0
- package/stacks/node/templates/root/CLAUDE.md +60 -0
- package/stacks/node/templates/root/docker-compose.yml +15 -0
- package/stacks/node/templates/root/flyway.conf +17 -0
- package/stacks/node/templates/root/migrations/V1__schema.sql.example +12 -0
- package/stacks/node/templates/root/package.json +50 -0
- package/stacks/node/templates/root/server.ts +130 -0
- package/stacks/node/templates/root/setup-env.sh +53 -0
- package/stacks/node/templates/root/src/common/assertions.ts +6 -0
- package/stacks/node/templates/root/src/common/db.ts +32 -0
- package/stacks/node/templates/root/src/common/loadPostgresEventstore.ts +23 -0
- package/stacks/node/templates/root/src/common/parseEndpoint.ts +51 -0
- package/stacks/node/templates/root/src/common/processorDlq.ts +28 -0
- package/stacks/node/templates/root/src/common/realtimeBroadcast.ts +12 -0
- package/stacks/node/templates/root/src/common/replay.ts +16 -0
- package/stacks/node/templates/root/src/common/routes.ts +19 -0
- package/stacks/node/templates/root/src/common/testHelpers.ts +44 -0
- package/stacks/node/templates/root/src/swagger.ts +34 -0
- package/stacks/node/templates/root/src/util/assertions.ts +6 -0
- package/stacks/node/templates/root/src/util/hash.ts +9 -0
- package/stacks/node/templates/root/src/util/sanitize.ts +23 -0
- package/stacks/node/templates/root/tsconfig.json +32 -0
- package/stacks/node/templates/root/vercel.json +8 -0
- package/stacks/supabase/templates/.claude/skills/build-automation/SKILL.md +266 -0
- package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +344 -0
- package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +390 -0
- package/stacks/supabase/templates/.claude/skills/build-webhook/SKILL.md +303 -0
- package/stacks/supabase/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/supabase/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
- package/stacks/supabase/templates/.claude/skills/load-slice/SKILL.md +143 -0
- package/stacks/supabase/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/supabase/templates/build-kit/README.md +86 -0
- package/stacks/supabase/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/supabase/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +152 -0
- package/stacks/supabase/templates/build-kit/lib/prompt.md +123 -0
- package/stacks/supabase/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/supabase/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/supabase/templates/build-kit/ralph.sh +98 -0
- package/stacks/supabase/templates/root/.env.example +22 -0
- package/stacks/supabase/templates/root/CLAUDE.md +61 -0
- package/stacks/supabase/templates/root/flyway.conf +17 -0
- package/stacks/supabase/templates/root/package.json +52 -0
- package/stacks/supabase/templates/root/server.ts +177 -0
- package/stacks/supabase/templates/root/setup-env.sh +53 -0
- package/stacks/supabase/templates/root/src/common/assertions.ts +6 -0
- package/stacks/supabase/templates/root/src/common/db.ts +32 -0
- package/stacks/supabase/templates/root/src/common/loadPostgresEventstore.ts +23 -0
- package/stacks/supabase/templates/root/src/common/parseEndpoint.ts +51 -0
- package/stacks/supabase/templates/root/src/common/processorDlq.ts +28 -0
- package/stacks/supabase/templates/root/src/common/realtimeBroadcast.ts +19 -0
- package/stacks/supabase/templates/root/src/common/replay.ts +16 -0
- package/stacks/supabase/templates/root/src/common/routes.ts +23 -0
- package/stacks/supabase/templates/root/src/common/testHelpers.ts +54 -0
- package/stacks/supabase/templates/root/src/supabase/README.md +141 -0
- package/stacks/supabase/templates/root/src/supabase/api.ts +56 -0
- package/stacks/supabase/templates/root/src/supabase/requireOrgaAdmin.ts +32 -0
- package/stacks/supabase/templates/root/src/supabase/requireSysUser.ts +40 -0
- package/stacks/supabase/templates/root/src/supabase/requireUser.ts +72 -0
- package/stacks/supabase/templates/root/src/swagger.ts +34 -0
- package/stacks/supabase/templates/root/src/util/assertions.ts +6 -0
- package/stacks/supabase/templates/root/src/util/hash.ts +9 -0
- package/stacks/supabase/templates/root/src/util/sanitize.ts +23 -0
- package/stacks/supabase/templates/root/supabase/config.toml +295 -0
- package/stacks/supabase/templates/root/supabase/migrations/V1__schema.sql.example +12 -0
- package/stacks/supabase/templates/root/supabase/seed.sql +1 -0
- package/stacks/supabase/templates/root/tsconfig.json +32 -0
- package/stacks/supabase/templates/root/vercel.json +8 -0
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eventmodeling-identifying-outputs
|
|
3
|
+
description: "Step 5 of Event Modeling - Identify Outputs/Read Models from events. Show what data flows back to UI and Processors. Use after defining inputs. Do not use for: identifying commands or inputs (use eventmodeling-identifying-inputs) or verifying field completeness (use eventmodeling-checking-completeness)."
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- AskUserQuestion
|
|
6
|
+
- Write
|
|
7
|
+
- Bash
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Identifying Outputs
|
|
11
|
+
|
|
12
|
+
> **Before doing anything else**, invoke the `connect` skill to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Then invoke the `learn-eventmodelers-api` skill to load the full API reference. Do not proceed until both skills have been loaded.
|
|
13
|
+
|
|
14
|
+
## Interview Phase (Optional)
|
|
15
|
+
|
|
16
|
+
**When to Interview**: Skip if the user has clearly identified: read model queries needed by UI, processor needs, and refresh patterns. Interview when unclear which data queries are critical or how frequently they're accessed.
|
|
17
|
+
|
|
18
|
+
**Interview Strategy**: Establish query patterns and identify any calculations before designing read models. The most common architecture error at this step is modeling recalculated state as an event — identifying calculated fields upfront prevents that anti-pattern.
|
|
19
|
+
|
|
20
|
+
### Critical Questions
|
|
21
|
+
|
|
22
|
+
1. **Query Patterns** (Impact: Determines which read models are needed and their update frequency)
|
|
23
|
+
- Question: "What data do users/processors need to query? (A) Real-time (sub-second), (B) Near-real-time (seconds), (C) Periodic (minutes/hours)?"
|
|
24
|
+
- Why it matters: Query frequency drives read model design and caching strategy
|
|
25
|
+
- Follow-up triggers: If (A) → ask which specific screens or processors require sub-second reads; these need dedicated, highly optimized read models
|
|
26
|
+
|
|
27
|
+
2. **Event vs Read Model Clarification** (Impact: Ensures we don't model calculations as events)
|
|
28
|
+
- Question: "Are there calculated/aggregated fields? (e.g., average rating, total sales, inventory count) - These are read models, not events."
|
|
29
|
+
- Why it matters: Common mistake to model calculations as events; identifying them upfront prevents architecture errors
|
|
30
|
+
- Follow-up triggers: For each calculated field mentioned → confirm "This recalculates as source data changes, so it belongs in a read model projection — does that match your expectation?"
|
|
31
|
+
|
|
32
|
+
### Interview Flow
|
|
33
|
+
|
|
34
|
+
**Conditional Entry**:
|
|
35
|
+
```
|
|
36
|
+
If user has provided:
|
|
37
|
+
- UI screens with data needs mapped to event sources
|
|
38
|
+
- AND processor query needs documented
|
|
39
|
+
- AND calculated/aggregated fields identified as read models (not events)
|
|
40
|
+
|
|
41
|
+
Then: Skip interview, proceed directly to read model design
|
|
42
|
+
|
|
43
|
+
Else: Conduct interview
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Phase 1: Query Pattern Mapping** (Question 1)
|
|
47
|
+
- Identify which UI screens and processors need which data
|
|
48
|
+
- Typically every screen needs some kind of data, same for automations.
|
|
49
|
+
- Establish freshness requirements per consumer
|
|
50
|
+
- Determine if any queries require real-time consistency
|
|
51
|
+
|
|
52
|
+
**Phase 2: Calculation Detection** (Question 2)
|
|
53
|
+
- Surface any aggregated or computed values
|
|
54
|
+
- Confirm they are projections, not events
|
|
55
|
+
- Prevent the calculation-as-event anti-pattern before design begins
|
|
56
|
+
|
|
57
|
+
### Capturing Interview Findings
|
|
58
|
+
|
|
59
|
+
Append findings to the project's event modeling file:
|
|
60
|
+
|
|
61
|
+
**File**: `.trogonai/interviews/[project-name]/EVENTMODELING.md`
|
|
62
|
+
|
|
63
|
+
Use Write tool to add/update this section:
|
|
64
|
+
|
|
65
|
+
```markdown
|
|
66
|
+
## 5. Identifying Outputs (eventmodeling-identifying-outputs)
|
|
67
|
+
|
|
68
|
+
### Query Patterns
|
|
69
|
+
[From Q1: Which consumers need what freshness? Real-time vs. periodic?]
|
|
70
|
+
|
|
71
|
+
### Calculated Fields Identified
|
|
72
|
+
[From Q2: Which fields are aggregated/calculated? Confirmed as read models?]
|
|
73
|
+
|
|
74
|
+
### Read Model Summary
|
|
75
|
+
- Real-time read models: [list]
|
|
76
|
+
- Near-real-time read models: [list]
|
|
77
|
+
- Calculation-as-event anti-patterns caught: [list or "None"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Update Interview Trail:
|
|
81
|
+
```markdown
|
|
82
|
+
| 5 | eventmodeling-identifying-outputs | Done | Read model catalog, query patterns, calculation classification |
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## CRITICAL: Events vs Read Models
|
|
88
|
+
|
|
89
|
+
**This is the most important distinction in event sourcing.** Many architectures fail because this line gets blurred.
|
|
90
|
+
|
|
91
|
+
### Events = Immutable Domain Facts
|
|
92
|
+
Things that actually happened in the domain. Once created, they never change:
|
|
93
|
+
- CustomerCreated (a customer actually signed up)
|
|
94
|
+
- OrderPlaced (someone actually placed an order)
|
|
95
|
+
- PaymentAuthorized (payment gateway actually authorized)
|
|
96
|
+
- OrderShipped (fulfillment actually shipped the order)
|
|
97
|
+
|
|
98
|
+
**Characteristics**:
|
|
99
|
+
- Represents an action someone took
|
|
100
|
+
- Immutable once recorded
|
|
101
|
+
- Can be replayed to rebuild state
|
|
102
|
+
- Provides audit trail
|
|
103
|
+
- Independent of other events
|
|
104
|
+
|
|
105
|
+
### Read Models = Derived Projections
|
|
106
|
+
Optimized views calculated FROM events. They recalculate multiple times:
|
|
107
|
+
- CustomerDashboard (projects current customer data)
|
|
108
|
+
- OrderStatusView (projects order state)
|
|
109
|
+
- InventoryLevelView (projects available stock from receipt/sale events)
|
|
110
|
+
- InventoryLevel (projects available stock)
|
|
111
|
+
|
|
112
|
+
**Characteristics**:
|
|
113
|
+
- Calculated/aggregated state
|
|
114
|
+
- Recalculates when source events change
|
|
115
|
+
- Derived from other events
|
|
116
|
+
- Query optimization
|
|
117
|
+
- Can be regenerated from events
|
|
118
|
+
|
|
119
|
+
### The Test: Is It an Event or Read Model?
|
|
120
|
+
|
|
121
|
+
Ask these questions in order:
|
|
122
|
+
|
|
123
|
+
| Question | Answer | Type | Example |
|
|
124
|
+
|----------|--------|------|---------|
|
|
125
|
+
| Did an actor perform an action? | YES | EVENT | Customer confirmed the order |
|
|
126
|
+
| Is this pure calculation? | YES | READ MODEL | Inventory level total |
|
|
127
|
+
| Is it immutable once created? | YES | EVENT | PaymentAuthorized |
|
|
128
|
+
| Does it recalculate multiple times? | YES | READ MODEL | Total sales (updates as orders change) |
|
|
129
|
+
| Is it independent (causes no other events)? | YES | EVENT | OrderFlagged (flagged for manual review) |
|
|
130
|
+
| Is it derived FROM other events? | YES | READ MODEL | OrderStatus (derived from multiple events) |
|
|
131
|
+
|
|
132
|
+
### Common Anti-Patterns
|
|
133
|
+
|
|
134
|
+
**DON'T model these as EVENTS**:
|
|
135
|
+
- Inventory level totals (calculation from stock events)
|
|
136
|
+
- Inventory totals (sum of transactions)
|
|
137
|
+
- Account balances (calculation from transactions)
|
|
138
|
+
- Search indexes (derived from documents)
|
|
139
|
+
- Aggregated metrics (sums, counts, averages)
|
|
140
|
+
- Scheduled calculations (processor outputs that are pure calculation)
|
|
141
|
+
|
|
142
|
+
**DO model them as READ MODELS**:
|
|
143
|
+
|
|
144
|
+
WRONG: Modeling as Event
|
|
145
|
+
```
|
|
146
|
+
InventoryLevelRecalculated
|
|
147
|
+
productId: product-456
|
|
148
|
+
currentStock: 84 (This recalculates!)
|
|
149
|
+
reservedStock: 12 (Derived, not a fact)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
CORRECT: Model as Read Model
|
|
153
|
+
```
|
|
154
|
+
InventoryLevelView
|
|
155
|
+
productId: product-456
|
|
156
|
+
totalReceived: 200
|
|
157
|
+
totalSold: 116
|
|
158
|
+
currentStock: 84
|
|
159
|
+
lastUpdated: 2025-01-24T10:30:00Z
|
|
160
|
+
history:
|
|
161
|
+
- 2024-12-01: stock 150 (200 received)
|
|
162
|
+
- 2024-12-15: stock 110 (40 sold)
|
|
163
|
+
- 2025-01-24: stock 84 (26 sold)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**WHY**:
|
|
167
|
+
- Events should capture facts (what happened)
|
|
168
|
+
- Calculations should be projections (how we view the facts)
|
|
169
|
+
- Otherwise you end up with circular dependencies and replay issues
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Workflow
|
|
174
|
+
|
|
175
|
+
Given commands and events, identify all outputs:
|
|
176
|
+
|
|
177
|
+
### 1. Map Event Data to UI Screens
|
|
178
|
+
For each screen, identify source events:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Screen: Order Status View
|
|
182
|
+
Displays data from events:
|
|
183
|
+
orderId ← OrderCreated event
|
|
184
|
+
customerId ← OrderCreated event
|
|
185
|
+
items ← OrderCreated event
|
|
186
|
+
total ← OrderCreated event
|
|
187
|
+
status ← OrderConfirmed event (or OrderCancelled)
|
|
188
|
+
confirmedAt ← OrderConfirmed event
|
|
189
|
+
paymentId ← PaymentAuthorized event
|
|
190
|
+
shipmentId ← OrderShipped event
|
|
191
|
+
shippedAt ← OrderShipped event
|
|
192
|
+
|
|
193
|
+
This screen is a projection of these events:
|
|
194
|
+
- OrderCreated
|
|
195
|
+
- OrderConfirmed
|
|
196
|
+
- PaymentAuthorized
|
|
197
|
+
- OrderShipped
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 2. Define Read Models
|
|
201
|
+
Create optimized views from event data:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
ReadModel: OrderStatusView
|
|
205
|
+
Purpose: UI displays current order status
|
|
206
|
+
Events subscribed: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
|
|
207
|
+
Data:
|
|
208
|
+
{
|
|
209
|
+
orderId: string (from OrderCreated)
|
|
210
|
+
customerId: string (from OrderCreated)
|
|
211
|
+
status: enum (from events: Draft → Confirmed → Authorized → Shipped → Delivered)
|
|
212
|
+
createdAt: Date (from OrderCreated)
|
|
213
|
+
confirmedAt: Date (from OrderConfirmed)
|
|
214
|
+
paymentId: string (from PaymentAuthorized)
|
|
215
|
+
shipmentId: string (from OrderShipped)
|
|
216
|
+
shippedAt: Date (from OrderShipped)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 3. Document Event → Data Mapping
|
|
221
|
+
Show exactly what data each event provides:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
Event: OrderCreated
|
|
225
|
+
Provides to UI/Processors:
|
|
226
|
+
orderId
|
|
227
|
+
customerId
|
|
228
|
+
items[]
|
|
229
|
+
total
|
|
230
|
+
shippingAddress
|
|
231
|
+
createdAt
|
|
232
|
+
|
|
233
|
+
Event: OrderConfirmed
|
|
234
|
+
Provides to UI/Processors:
|
|
235
|
+
orderId (link to stream)
|
|
236
|
+
paymentMethod (user selected method)
|
|
237
|
+
confirmedAt (timestamp)
|
|
238
|
+
paymentId (payment system reference)
|
|
239
|
+
|
|
240
|
+
Event: PaymentAuthorized
|
|
241
|
+
Provides to UI/Processors:
|
|
242
|
+
orderId (link to stream)
|
|
243
|
+
paymentId
|
|
244
|
+
authCode
|
|
245
|
+
authorizedAt (timestamp)
|
|
246
|
+
amount (verified amount)
|
|
247
|
+
|
|
248
|
+
Event: OrderShipped
|
|
249
|
+
Provides to UI/Processors:
|
|
250
|
+
orderId (link to stream)
|
|
251
|
+
shipmentId
|
|
252
|
+
shippedAt (timestamp)
|
|
253
|
+
carrier (shipping company)
|
|
254
|
+
trackingNumber (for delivery tracking)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 4. Create Output Catalog
|
|
258
|
+
List all read models:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
ReadModel Catalog: Order System
|
|
262
|
+
|
|
263
|
+
1. OrderStatusReadModel
|
|
264
|
+
Purpose: UI shows current order status
|
|
265
|
+
Events: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
|
|
266
|
+
Data: orderId, status, createdAt, confirmedAt, paymentId, shipmentId
|
|
267
|
+
Consumed by:
|
|
268
|
+
- Order Status screen (UI)
|
|
269
|
+
- Customer Dashboard (UI)
|
|
270
|
+
- Order Processing Processor (decides if can ship)
|
|
271
|
+
|
|
272
|
+
2. OrderListReadModel
|
|
273
|
+
Purpose: UI lists all orders for a customer
|
|
274
|
+
Events: OrderCreated, OrderConfirmed, OrderCancelled
|
|
275
|
+
Data: orderId, customerId, total, status, createdAt
|
|
276
|
+
Consumed by:
|
|
277
|
+
- Customer Order History (UI)
|
|
278
|
+
- Order Search/Filter (UI)
|
|
279
|
+
|
|
280
|
+
3. PaymentStatusReadModel
|
|
281
|
+
Purpose: UI shows payment status
|
|
282
|
+
Events: OrderConfirmed, PaymentAuthorized, PaymentFailed
|
|
283
|
+
Data: orderId, paymentId, status, authCode, failureReason, timestamp
|
|
284
|
+
Consumed by:
|
|
285
|
+
- Payment Status screen (UI)
|
|
286
|
+
- Accounting Processor (reconciliation)
|
|
287
|
+
|
|
288
|
+
4. ShipmentTrackingReadModel
|
|
289
|
+
Purpose: UI shows tracking information
|
|
290
|
+
Events: OrderShipped, DeliveryConfirmed
|
|
291
|
+
Data: orderId, shipmentId, trackingNumber, carrier, shippedAt, estimatedDelivery
|
|
292
|
+
Consumed by:
|
|
293
|
+
- Order Tracking screen (UI)
|
|
294
|
+
- Customer notifications (Processor)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 5. Identify Missing Data
|
|
298
|
+
Check if all UI needs are covered:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
Question: What if UI needs "estimated delivery date"?
|
|
302
|
+
Event: OrderShipped has carrier + trackingNumber
|
|
303
|
+
Action needed: Add estimatedDelivery to OrderShipped event
|
|
304
|
+
(or compute from carrier info)
|
|
305
|
+
|
|
306
|
+
Question: What if UI needs to show "payment method" on status?
|
|
307
|
+
Event: OrderConfirmed has paymentMethod
|
|
308
|
+
Action needed: Include paymentMethod in relevant read models
|
|
309
|
+
|
|
310
|
+
Question: What if UI needs "item descriptions"?
|
|
311
|
+
Event: OrderCreated has items[]
|
|
312
|
+
But: items[] only has productId
|
|
313
|
+
Action needed: Enrich with product descriptions from catalog
|
|
314
|
+
(via join with product service)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 6. Processor Outputs
|
|
318
|
+
Identify what processors consume:
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
Processor: Inventory System
|
|
322
|
+
Consumes from read models:
|
|
323
|
+
- Orders in "PaymentAuthorized" status
|
|
324
|
+
- Items and quantities needed
|
|
325
|
+
Produces commands:
|
|
326
|
+
- ReserveInventory
|
|
327
|
+
|
|
328
|
+
Processor: Fulfillment System
|
|
329
|
+
Consumes from read models:
|
|
330
|
+
- Orders in "InventoryReserved" status
|
|
331
|
+
- Items and quantities
|
|
332
|
+
- Shipping address
|
|
333
|
+
Produces commands:
|
|
334
|
+
- CreateShipment
|
|
335
|
+
|
|
336
|
+
Processor: Notification System
|
|
337
|
+
Consumes from read models:
|
|
338
|
+
- OrderCreated (sends confirmation)
|
|
339
|
+
- OrderConfirmed (sends receipt)
|
|
340
|
+
- OrderShipped (sends tracking)
|
|
341
|
+
- DeliveryConfirmed (sends thank you)
|
|
342
|
+
Does not produce commands (info-only)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Output Format
|
|
346
|
+
|
|
347
|
+
Instead of writing a markdown document, **place each READMODEL (and any missing AUTOMATION) on the board** using the `node:created` API. Screens are typically already placed from Step 3 (storyboarding) — do not re-place them unless one is clearly missing. Automations are placed here when analysis reveals a processor that reads state and issues commands but is not yet on the board.
|
|
348
|
+
|
|
349
|
+
> **CRITICAL: Every READMODEL node MUST include `meta.fields` with a `mapping` on every field.** A read model without fields — or with fields that lack `mapping` — has no data lineage and cannot be traced back to its source events.
|
|
350
|
+
|
|
351
|
+
### The typical slice pattern
|
|
352
|
+
|
|
353
|
+
The standard pattern for a screen in an event model is:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
READ MODEL → SCREEN → COMMAND → EVENT
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Most screens need a read model on their left** — not only view/status screens, but also command/input screens that show current state before the user acts (e.g., a booking form that displays a bike's current availability, a checkout screen that shows the cart). The read model feeds the screen; the screen triggers a command; the command produces an event.
|
|
360
|
+
|
|
361
|
+
Pure view screens (no outgoing command) follow a shorter pattern:
|
|
362
|
+
```
|
|
363
|
+
READ MODEL → SCREEN
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Automations follow:
|
|
367
|
+
```
|
|
368
|
+
READ MODEL → AUTOMATION → COMMAND → EVENT
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Treat any screen or automation without an incoming read model as a gap unless it provably needs no prior state at all (e.g., a blank registration form).
|
|
372
|
+
|
|
373
|
+
### Read models serve existing screens and automations
|
|
374
|
+
|
|
375
|
+
**Before designing any read model, enumerate every SCREEN and AUTOMATION already placed on the board** (from Step 3 — Storyboarding). Read models exist to serve those elements:
|
|
376
|
+
|
|
377
|
+
- Every **view screen** (output/read model screen) needs at least one read model to supply its data.
|
|
378
|
+
- Every **automation** that makes a decision based on system state needs at least one read model to read from.
|
|
379
|
+
- Every **command/input screen** needs a read model unless it is a blank creation form with no prior state to display (this is the rare exception, not the rule).
|
|
380
|
+
|
|
381
|
+
After the step is done, **every SCREEN and every AUTOMATION on the board must be connected to at least one read model** via a `READMODEL → SCREEN` or `READMODEL → AUTOMATION` connection. If a screen or automation has no incoming read model connection, it is a gap — either a read model is missing or the connection arrow is missing.
|
|
382
|
+
|
|
383
|
+
> **Placement rule**: A read model must be placed in a column that already contains a SCREEN or AUTOMATION it serves. Do not place read models in columns with no screen or automation — doing so creates orphaned read models that will never have a consumer.
|
|
384
|
+
|
|
385
|
+
### Field data lineage — the `mapping` attribute on READMODEL fields
|
|
386
|
+
|
|
387
|
+
Every field on a READMODEL must carry a `mapping` that says exactly which event (or command) field it is projected from. Use one of these forms:
|
|
388
|
+
|
|
389
|
+
| `mapping` format | Meaning | `generated` | Example |
|
|
390
|
+
|---|---|---|---|
|
|
391
|
+
| `"<EventTitle>.<fieldName>"` | Projected directly from an event field | `false` | `"BikeReserved.customerId"` |
|
|
392
|
+
| `"latest:<EventTitle>.<fieldName>"` | Latest value from the most recent event of this type | `false` | `"latest:BikeStatusChanged.toStatus"` |
|
|
393
|
+
| `"aggregate:<EventTitle>.<fieldName>"` | Aggregated across multiple events of this type | `true` | `"aggregate:RentalEnded.durationMinutes"` |
|
|
394
|
+
| `"derived:<expression>"` | Calculated from other read model fields | `true` | `"derived:sum(lineItems.amount)"` |
|
|
395
|
+
|
|
396
|
+
Set the field's `generated` property according to this table. Fields projected directly from events are not generated — they carry a real domain value. Fields that are aggregated or calculated by the system are generated.
|
|
397
|
+
|
|
398
|
+
**Field traceability rule**: Every field in a read model must trace back to at least one source event. If a field cannot be mapped to any event in the timeline, it is either:
|
|
399
|
+
- A calculated/derived field — document the derivation expression, or
|
|
400
|
+
- A missing event field — add it to the source event before proceeding.
|
|
401
|
+
|
|
402
|
+
> **Connected-element rule**: A read model's field `mapping` may only reference EVENTs that are connected to this READMODEL via a board `EVENT → READMODEL` arrow. If a field needs data from an event that is not connected, either add the connection or flag it as a gap. **If a mapping cannot be defined for a field, it signals missing data in the model, a missing event, or a modeling error.** Do not leave unmapped fields without a note.
|
|
403
|
+
|
|
404
|
+
Every field must also set `"cardinality"` — use `"Single"` unless the field genuinely holds a list of values (e.g. line items, a collection projected from multiple events), in which case use `"List"`. Default to `"Single"` when unsure. Include only the fields the consuming SCREEN or AUTOMATION actually displays or needs — do not add speculative fields; enrich later via `/attributes`.
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{
|
|
408
|
+
"type": "READMODEL",
|
|
409
|
+
"title": "ActiveReservationView",
|
|
410
|
+
"fields": [
|
|
411
|
+
{"name": "reservationId", "type": "String", "example": "res-001", "mapping": "BikeReserved.reservationId", "generated": false},
|
|
412
|
+
{"name": "customerId", "type": "String", "example": "cust-42", "mapping": "BikeReserved.customerId", "generated": false},
|
|
413
|
+
{"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "BikeReserved.bikeId", "generated": false},
|
|
414
|
+
{"name": "stationId", "type": "String", "example": "stn-03", "mapping": "BikeReserved.stationId", "generated": false},
|
|
415
|
+
{"name": "expiresAt", "type": "Date", "example": "2026-06-01T09:30:00Z", "mapping": "ReservationConfirmed.expiresAt", "generated": false},
|
|
416
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "latest:ReservationConfirmed.status", "generated": false}
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Read models go in the `interaction` lane — **in the same column as the SCREEN or AUTOMATION they serve** (READMODEL in interaction row, SCREEN in actor row of the same column).
|
|
422
|
+
|
|
423
|
+
> **Timeline alignment rule**: Place the read model in the same column as its consumer screen/automation. If that column already holds a COMMAND (state-change slice occupies the interaction row), insert a new column immediately after (`{"index": N+1}`) and place both the READMODEL and any new SCREEN there. Do not append read model columns to the end of the timeline — doing so severs the visual left→right flow from data projection to UI consumption.
|
|
424
|
+
|
|
425
|
+
### Placing READMODEL and AUTOMATION nodes with `cellId` (Mandatory)
|
|
426
|
+
|
|
427
|
+
**Every `node:created` call MUST include `cellId`.** Without it the node has no cell reference and will appear stranded at position 0,0 — not in any timeline column.
|
|
428
|
+
|
|
429
|
+
**For a READMODEL** (interaction lane):
|
|
430
|
+
|
|
431
|
+
1. Find the column where the consumer SCREEN or AUTOMATION lives. Fetch the timeline to get the interaction row ID:
|
|
432
|
+
```bash
|
|
433
|
+
curl -s -H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" \
|
|
434
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
|
|
435
|
+
# → timelineData.rows — find the row where type === "interaction"
|
|
436
|
+
```
|
|
437
|
+
2. Check if the interaction cell is already occupied (existing COMMAND):
|
|
438
|
+
```bash
|
|
439
|
+
curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<interactionRowId>-<columnId>" \
|
|
440
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID"
|
|
441
|
+
```
|
|
442
|
+
If a COMMAND occupies that cell, insert a new column immediately after (`{"index": currentIndex + 1}`) and use that column's ID instead.
|
|
443
|
+
3. `cellId = interactionRow.id + "-" + columnId`
|
|
444
|
+
4. Create the READMODEL:
|
|
445
|
+
```bash
|
|
446
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
447
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: identifying-outputs" \
|
|
448
|
+
-H "Content-Type: application/json" \
|
|
449
|
+
-d '[{
|
|
450
|
+
"id": "<event-uuid>",
|
|
451
|
+
"eventType": "node:created",
|
|
452
|
+
"nodeId": "<node-uuid>",
|
|
453
|
+
"boardId": "<BOARD_ID>",
|
|
454
|
+
"timestamp": 1234567890,
|
|
455
|
+
"chapterId": "<CHAPTER_ID>",
|
|
456
|
+
"cellId": "<interactionRowId>-<columnId>",
|
|
457
|
+
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|
|
458
|
+
}]'
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**For an AUTOMATION** (actor lane, same column as its READMODEL):
|
|
462
|
+
|
|
463
|
+
1. Get the actor row ID from the same timeline fetch (row where `type === "actor"`).
|
|
464
|
+
2. `cellId = actorRow.id + "-" + columnId`
|
|
465
|
+
3. Create the AUTOMATION node using the same `node:created` pattern above with `"type": "AUTOMATION"` in `meta`.
|
|
466
|
+
|
|
467
|
+
> **Never call `drop` after using `cellId` in `node:created`.** The drop endpoint adds a second cell reference without removing the first. `node:created + cellId` is the only placement step needed.
|
|
468
|
+
|
|
469
|
+
### Preventing backward arrows (mandatory pre-placement check)
|
|
470
|
+
|
|
471
|
+
The timeline must always progress left-to-right. A `READMODEL → SCREEN` connection going right-to-left is a layout error.
|
|
472
|
+
|
|
473
|
+
The correct layout is: **READMODEL in column N, SCREEN in column N+1** (the screen is always one column to the right of its read model). Before placing each read model, find the view screen it serves and verify the column order:
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
For each view screen S that queries this read model:
|
|
477
|
+
If column(S) <= intended column(READMODEL):
|
|
478
|
+
→ The screen is not to the right of the read model. Fix before placing.
|
|
479
|
+
Option A: Insert a new column immediately after the read model's column
|
|
480
|
+
and move screen S there.
|
|
481
|
+
Use POST /timelines/:tl/columns {"index": N} to insert,
|
|
482
|
+
then node:changed to update the screen node's cell.
|
|
483
|
+
If column(S) == intended column(READMODEL) + 1:
|
|
484
|
+
→ Screen is already in the correct column directly to the right. No adjustment needed.
|
|
485
|
+
If column(S) > intended column(READMODEL) + 1:
|
|
486
|
+
→ Gap between read model and screen. Move the read model to column(S) - 1, or move the screen to column(READMODEL) + 1.
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**View screens go in the column immediately to the right of the read model they display** — either because they were placed there in Step 3, or because you move them here now.
|
|
490
|
+
|
|
491
|
+
### Wire connections after placing each READMODEL (and its SCREEN)
|
|
492
|
+
|
|
493
|
+
After `place-element` returns the READMODEL node ID, create the arrows that complete the slice:
|
|
494
|
+
|
|
495
|
+
1. **EVENT → READMODEL** — find the primary source EVENT node in the swimlane row of the same column:
|
|
496
|
+
```bash
|
|
497
|
+
curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>" \
|
|
498
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs"
|
|
499
|
+
```
|
|
500
|
+
Connect it:
|
|
501
|
+
```bash
|
|
502
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
503
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
504
|
+
-H "Content-Type: application/json" \
|
|
505
|
+
-d '{"source":"<eventNodeId>","target":"<readmodelNodeId>"}'
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
2. **READMODEL → SCREEN** — connect to the existing SCREEN node in the actor row of the next column (screens are typically already placed from Step 3):
|
|
509
|
+
```bash
|
|
510
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
511
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
512
|
+
-H "Content-Type: application/json" \
|
|
513
|
+
-d '{"source":"<readmodelNodeId>","target":"<screenNodeId>"}'
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
3. **READMODEL → AUTOMATION** — if the read model is consumed by an automatic process (scheduler, background job, external trigger), place the AUTOMATION node and connect it:
|
|
517
|
+
- Place the AUTOMATION in the automation lane, in the column immediately to the right of its read model (same rule as screens).
|
|
518
|
+
- Then connect:
|
|
519
|
+
```bash
|
|
520
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
521
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
522
|
+
-H "Content-Type: application/json" \
|
|
523
|
+
-d '{"source":"<readmodelNodeId>","target":"<automationNodeId>"}'
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Skip a connection silently if the target cell is empty. Log each created arrow: `→ connected EVENT→READMODEL "OrderPlaced"→"OrderStatusView"`, `→ connected READMODEL→SCREEN "OrderStatusView"→"Order Status Screen"`, or `→ connected READMODEL→AUTOMATION "OrderStatusView"→"Fulfillment Processor"`.
|
|
527
|
+
|
|
528
|
+
After all read models, screens, automations, and connections are in place, present the Read Model Catalog summary as text to the user.
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
For reference, the full markdown structure is:
|
|
533
|
+
|
|
534
|
+
```markdown
|
|
535
|
+
# Outputs: [Domain Name]
|
|
536
|
+
|
|
537
|
+
## Read Models Summary
|
|
538
|
+
|
|
539
|
+
| ReadModel | Purpose | Events | Consumed By |
|
|
540
|
+
|-----------|---------|--------|-------------|
|
|
541
|
+
| OrderStatus | Show order state | OrderCreated, OrderConfirmed | UI, Processor |
|
|
542
|
+
| OrderList | List orders | OrderCreated, OrderCancelled | UI |
|
|
543
|
+
| PaymentStatus | Payment info | OrderConfirmed, PaymentAuthorized | UI, Accounting |
|
|
544
|
+
| Shipment Tracking | Track delivery | OrderShipped, DeliveryConfirmed | UI, Notifications |
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Detailed Read Models
|
|
549
|
+
|
|
550
|
+
### ReadModel: OrderStatusView
|
|
551
|
+
|
|
552
|
+
**Purpose**: Order Status screen displays current order state
|
|
553
|
+
|
|
554
|
+
**Events subscribed**:
|
|
555
|
+
- OrderCreated
|
|
556
|
+
- OrderConfirmed
|
|
557
|
+
- PaymentAuthorized
|
|
558
|
+
- OrderShipped
|
|
559
|
+
- OrderCancelled
|
|
560
|
+
- DeliveryConfirmed
|
|
561
|
+
|
|
562
|
+
**Data**:
|
|
563
|
+
```
|
|
564
|
+
{
|
|
565
|
+
orderId: string
|
|
566
|
+
customerId: string
|
|
567
|
+
status: 'Draft' | 'Confirmed' | 'Authorized' | 'Shipped' | 'Delivered' | 'Cancelled'
|
|
568
|
+
items: Array<{productId, quantity, unitPrice}>
|
|
569
|
+
total: number
|
|
570
|
+
shippingAddress: Address
|
|
571
|
+
|
|
572
|
+
createdAt: Date
|
|
573
|
+
confirmedAt: Date
|
|
574
|
+
paymentId: string
|
|
575
|
+
paymentMethod: 'card' | 'transfer'
|
|
576
|
+
authorizedAt: Date
|
|
577
|
+
|
|
578
|
+
shipmentId: string
|
|
579
|
+
carrier: string
|
|
580
|
+
trackingNumber: string
|
|
581
|
+
shippedAt: Date
|
|
582
|
+
estimatedDelivery: Date
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Update Logic**:
|
|
587
|
+
- OrderCreated: Insert with status='Draft'
|
|
588
|
+
- OrderConfirmed: Update status='Confirmed'
|
|
589
|
+
- PaymentAuthorized: Update status='Authorized', set paymentId
|
|
590
|
+
- OrderShipped: Update status='Shipped', set shipmentId, carrier, trackingNumber
|
|
591
|
+
- DeliveryConfirmed: Update status='Delivered'
|
|
592
|
+
- OrderCancelled: Update status='Cancelled'
|
|
593
|
+
|
|
594
|
+
**Consumed By**:
|
|
595
|
+
- Order Status Screen (displays)
|
|
596
|
+
- Order Processing Processor (checks status)
|
|
597
|
+
- Notification System (sends updates)
|
|
598
|
+
|
|
599
|
+
--- [Repeat for each read model]
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Data Completeness Check
|
|
604
|
+
|
|
605
|
+
### Events → UI Needs
|
|
606
|
+
|
|
607
|
+
Verify all UI needs have event sources:
|
|
608
|
+
|
|
609
|
+
| UI Need | Event Source | Status |
|
|
610
|
+
|---------|-------------|--------|
|
|
611
|
+
| Order status | OrderConfirmed, OrderShipped | |
|
|
612
|
+
| Tracking number | OrderShipped | |
|
|
613
|
+
| Order items | OrderCreated | |
|
|
614
|
+
| Estimated delivery | OrderShipped | |
|
|
615
|
+
| Cancellation reason | OrderCancelled | |
|
|
616
|
+
|
|
617
|
+
### Missing Data
|
|
618
|
+
|
|
619
|
+
Identify UI needs without event sources:
|
|
620
|
+
- None identified
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## Processor Consumption
|
|
625
|
+
|
|
626
|
+
### Processors and their reads:
|
|
627
|
+
|
|
628
|
+
| Processor | Reads From | Writes Commands |
|
|
629
|
+
|-----------|-----------|-----------------|
|
|
630
|
+
| Inventory | OrderStatusView (Authorized) | ReserveInventory |
|
|
631
|
+
| Fulfillment | OrderStatusView (InventoryReserved) | CreateShipment |
|
|
632
|
+
| Notification | OrderStatusView (all) | None (info-only) |
|
|
633
|
+
| Accounting | PaymentStatusView | None (reporting) |
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## Quality Checklist
|
|
637
|
+
|
|
638
|
+
### Read Model Design
|
|
639
|
+
- [ ] **Typical pattern applied**: most screens follow `READ MODEL → SCREEN → COMMAND → EVENT`
|
|
640
|
+
- [ ] **Every SCREEN from storyboarding is connected to at least one read model** (via `READMODEL → SCREEN`); only blank creation forms may be exempt
|
|
641
|
+
- [ ] **Every AUTOMATION from storyboarding is connected to at least one read model** (via `READMODEL → AUTOMATION`)
|
|
642
|
+
- [ ] **No read model is placed without a connected SCREEN or AUTOMATION consumer**
|
|
643
|
+
- [ ] Every read model has clear purpose
|
|
644
|
+
- [ ] Every data field has event source
|
|
645
|
+
- [ ] Update logic for each event is explicit
|
|
646
|
+
- [ ] All UI needs are covered
|
|
647
|
+
- [ ] Processor reads are identified
|
|
648
|
+
- [ ] Read model access patterns clear
|
|
649
|
+
- [ ] No undocumented data sources
|
|
650
|
+
- [ ] Compensation/cancellation handled
|
|
651
|
+
- [ ] Error states shown
|
|
652
|
+
|
|
653
|
+
### CRITICAL: Event vs Read Model Validation
|
|
654
|
+
- [ ] **Reviewed each read model**: "Is this pure calculation or an actual domain fact?"
|
|
655
|
+
- [ ] **No aggregations modeled as events**: (totals, averages, counts are read models)
|
|
656
|
+
- [ ] **No recalculated state modeled as events**: (if value changes multiple times, it's a read model)
|
|
657
|
+
- [ ] **Processor outputs are categorized**:
|
|
658
|
+
- [ ] Produces NEW EVENT = actual domain fact (e.g., PaymentAuthorized)
|
|
659
|
+
- [ ] Updates READ MODEL = calculation (e.g., SellerRatingCalculated)
|
|
660
|
+
- [ ] Sends NOTIFICATION = info-only (no event or model)
|
|
661
|
+
- [ ] **History tracking is clear**: Derived state keeps history in read model `history[]`, not as separate events
|
|
662
|
+
|
|
663
|
+
## Key Principles
|
|
664
|
+
|
|
665
|
+
1. **Event-Driven**: All data comes from events
|
|
666
|
+
2. **Projection-Based**: Read models are projections, not persistent
|
|
667
|
+
3. **UI-Focused**: Optimized for UI display needs
|
|
668
|
+
4. **Processor-Friendly**: Enough data for processor decisions
|
|
669
|
+
5. **Completeness**: All needed data available
|
|
670
|
+
|
|
671
|
+
## Common Patterns
|
|
672
|
+
|
|
673
|
+
### Status View Pattern
|
|
674
|
+
```
|
|
675
|
+
Events: Create, Confirm, Process, Ship
|
|
676
|
+
ReadModel: Accumulates data from all events
|
|
677
|
+
Displayed: Current state reflecting all events
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### List View Pattern
|
|
681
|
+
```
|
|
682
|
+
Events: Create, Update, Delete (Cancel)
|
|
683
|
+
ReadModel: Summary of each item
|
|
684
|
+
Used for: Filtering, sorting, searching
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Timeline View Pattern
|
|
688
|
+
```
|
|
689
|
+
Events: Any event with timestamp
|
|
690
|
+
ReadModel: Chronological list
|
|
691
|
+
Used for: History, audit trail
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Processor Decision Pattern
|
|
695
|
+
```
|
|
696
|
+
Events: State-changing events
|
|
697
|
+
ReadModel: Current state only
|
|
698
|
+
Processor reads to decide next action
|
|
699
|
+
```
|