eventmodeling-identifying-outputs

Installation
SKILL.md

Identifying Outputs

Interview Phase (Optional)

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.

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.

Critical Questions

  1. Query Patterns (Impact: Determines which read models are needed and their update frequency)

    • Question: "What data do users/processors need to query? (A) Real-time (sub-second), (B) Near-real-time (seconds), (C) Periodic (minutes/hours)?"
    • Why it matters: Query frequency drives read model design and caching strategy
    • Follow-up triggers: If (A) → ask which specific screens or processors require sub-second reads; these need dedicated, highly optimized read models
  2. Event vs Read Model Clarification (Impact: Ensures we don't model calculations as events)

    • Question: "Are there calculated/aggregated fields? (e.g., average rating, total sales, inventory count) - These are read models, not events."
    • Why it matters: Common mistake to model calculations as events; identifying them upfront prevents architecture errors
    • 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?"

Interview Flow

Conditional Entry:

If user has provided:
  - UI screens with data needs mapped to event sources
  - AND processor query needs documented
  - AND calculated/aggregated fields identified as read models (not events)

Then: Skip interview, proceed directly to read model design

Else: Conduct interview

Phase 1: Query Pattern Mapping (Question 1)

  • Identify which UI screens and processors need data
  • Establish freshness requirements per consumer
  • Determine if any queries require real-time consistency

Phase 2: Calculation Detection (Question 2)

  • Surface any aggregated or computed values
  • Confirm they are projections, not events
  • Prevent the calculation-as-event anti-pattern before design begins

Capturing Interview Findings

Append findings to the project's event modeling file:

File: .trogonai/interviews/[project-name]/EVENTMODELING.md

Use Write tool to add/update this section:

## 5. Identifying Outputs (eventmodeling-identifying-outputs)

### Query Patterns
[From Q1: Which consumers need what freshness? Real-time vs. periodic?]

### Calculated Fields Identified
[From Q2: Which fields are aggregated/calculated? Confirmed as read models?]

### Read Model Summary
- Real-time read models: [list]
- Near-real-time read models: [list]
- Calculation-as-event anti-patterns caught: [list or "None"]

Update Interview Trail:

| 5 | eventmodeling-identifying-outputs | Done | Read model catalog, query patterns, calculation classification |

CRITICAL: Events vs Read Models

This is the most important distinction in event sourcing. Many architectures fail because this line gets blurred.

Events = Immutable Domain Facts

Things that actually happened in the domain. Once created, they never change:

  • CustomerCreated (a customer actually signed up)
  • OrderPlaced (someone actually placed an order)
  • PaymentAuthorized (payment gateway actually authorized)
  • OrderShipped (fulfillment actually shipped the order)

Characteristics:

  • Represents an action someone took
  • Immutable once recorded
  • Can be replayed to rebuild state
  • Provides audit trail
  • Independent of other events

Read Models = Derived Projections

Optimized views calculated FROM events. They recalculate multiple times:

  • CustomerDashboard (projects current customer data)
  • OrderStatusView (projects order state)
  • InventoryLevelView (projects available stock from receipt/sale events)
  • InventoryLevel (projects available stock)

Characteristics:

  • Calculated/aggregated state
  • Recalculates when source events change
  • Derived from other events
  • Query optimization
  • Can be regenerated from events

The Test: Is It an Event or Read Model?

Ask these questions in order:

Question Answer Type Example
Did an actor perform an action? YES EVENT Customer confirmed the order
Is this pure calculation? YES READ MODEL Inventory level total
Is it immutable once created? YES EVENT PaymentAuthorized
Does it recalculate multiple times? YES READ MODEL Total sales (updates as orders change)
Is it independent (causes no other events)? YES EVENT OrderFlagged (flagged for manual review)
Is it derived FROM other events? YES READ MODEL OrderStatus (derived from multiple events)

Common Anti-Patterns

DON'T model these as EVENTS:

  • Inventory level totals (calculation from stock events)
  • Inventory totals (sum of transactions)
  • Account balances (calculation from transactions)
  • Search indexes (derived from documents)
  • Aggregated metrics (sums, counts, averages)
  • Scheduled calculations (processor outputs that are pure calculation)

DO model them as READ MODELS:

WRONG: Modeling as Event

InventoryLevelRecalculated
  productId: product-456
  currentStock: 84          (This recalculates!)
  reservedStock: 12         (Derived, not a fact)

CORRECT: Model as Read Model

InventoryLevelView
  productId: product-456
  totalReceived: 200
  totalSold: 116
  currentStock: 84
  lastUpdated: 2025-01-24T10:30:00Z
  history:
    - 2024-12-01: stock 150 (200 received)
    - 2024-12-15: stock 110 (40 sold)
    - 2025-01-24: stock 84 (26 sold)

WHY:

  • Events should capture facts (what happened)
  • Calculations should be projections (how we view the facts)
  • Otherwise you end up with circular dependencies and replay issues

Workflow

Given commands and events, identify all outputs:

1. Map Event Data to UI Screens

For each screen, identify source events:

Screen: Order Status View
Displays data from events:
  orderId ← OrderCreated event
  customerId ← OrderCreated event
  items ← OrderCreated event
  total ← OrderCreated event
  status ← OrderConfirmed event (or OrderCancelled)
  confirmedAt ← OrderConfirmed event
  paymentId ← PaymentAuthorized event
  shipmentId ← OrderShipped event
  shippedAt ← OrderShipped event

This screen is a projection of these events:
  - OrderCreated
  - OrderConfirmed
  - PaymentAuthorized
  - OrderShipped

2. Define Read Models

Create optimized views from event data:

ReadModel: OrderStatusView
Purpose: UI displays current order status
Events subscribed: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
Data:
{
  orderId: string (from OrderCreated)
  customerId: string (from OrderCreated)
  status: enum (from events: Draft → Confirmed → Authorized → Shipped → Delivered)
  createdAt: Date (from OrderCreated)
  confirmedAt: Date (from OrderConfirmed)
  paymentId: string (from PaymentAuthorized)
  shipmentId: string (from OrderShipped)
  shippedAt: Date (from OrderShipped)
}

3. Document Event → Data Mapping

Show exactly what data each event provides:

Event: OrderCreated
Provides to UI/Processors:
  orderId
  customerId
  items[]
  total
  shippingAddress
  createdAt

Event: OrderConfirmed
Provides to UI/Processors:
  orderId (link to stream)
  paymentMethod (user selected method)
  confirmedAt (timestamp)
  paymentId (payment system reference)

Event: PaymentAuthorized
Provides to UI/Processors:
  orderId (link to stream)
  paymentId
  authCode
  authorizedAt (timestamp)
  amount (verified amount)

Event: OrderShipped
Provides to UI/Processors:
  orderId (link to stream)
  shipmentId
  shippedAt (timestamp)
  carrier (shipping company)
  trackingNumber (for delivery tracking)

4. Create Output Catalog

List all read models:

ReadModel Catalog: Order System

1. OrderStatusReadModel
   Purpose: UI shows current order status
   Events: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
   Data: orderId, status, createdAt, confirmedAt, paymentId, shipmentId
   Consumed by:
     - Order Status screen (UI)
     - Customer Dashboard (UI)
     - Order Processing Processor (decides if can ship)

2. OrderListReadModel
   Purpose: UI lists all orders for a customer
   Events: OrderCreated, OrderConfirmed, OrderCancelled
   Data: orderId, customerId, total, status, createdAt
   Consumed by:
     - Customer Order History (UI)
     - Order Search/Filter (UI)

3. PaymentStatusReadModel
   Purpose: UI shows payment status
   Events: OrderConfirmed, PaymentAuthorized, PaymentFailed
   Data: orderId, paymentId, status, authCode, failureReason, timestamp
   Consumed by:
     - Payment Status screen (UI)
     - Accounting Processor (reconciliation)

4. ShipmentTrackingReadModel
   Purpose: UI shows tracking information
   Events: OrderShipped, DeliveryConfirmed
   Data: orderId, shipmentId, trackingNumber, carrier, shippedAt, estimatedDelivery
   Consumed by:
     - Order Tracking screen (UI)
     - Customer notifications (Processor)

5. Identify Missing Data

Check if all UI needs are covered:

Question: What if UI needs "estimated delivery date"?
Event: OrderShipped has carrier + trackingNumber
Action needed: Add estimatedDelivery to OrderShipped event
  (or compute from carrier info)

Question: What if UI needs to show "payment method" on status?
Event: OrderConfirmed has paymentMethod
Action needed: Include paymentMethod in relevant read models

Question: What if UI needs "item descriptions"?
Event: OrderCreated has items[]
But: items[] only has productId
Action needed: Enrich with product descriptions from catalog
  (via join with product service)

6. Processor Outputs

Identify what processors consume:

Processor: Inventory System
Consumes from read models:
  - Orders in "PaymentAuthorized" status
  - Items and quantities needed
Produces commands:
  - ReserveInventory

Processor: Fulfillment System
Consumes from read models:
  - Orders in "InventoryReserved" status
  - Items and quantities
  - Shipping address
Produces commands:
  - CreateShipment

Processor: Notification System
Consumes from read models:
  - OrderCreated (sends confirmation)
  - OrderConfirmed (sends receipt)
  - OrderShipped (sends tracking)
  - DeliveryConfirmed (sends thank you)
Does not produce commands (info-only)

Output Format

Present as:

# Outputs: [Domain Name]

## Read Models Summary

| ReadModel | Purpose | Events | Consumed By |
|-----------|---------|--------|-------------|
| OrderStatus | Show order state | OrderCreated, OrderConfirmed | UI, Processor |
| OrderList | List orders | OrderCreated, OrderCancelled | UI |
| PaymentStatus | Payment info | OrderConfirmed, PaymentAuthorized | UI, Accounting |
| Shipment Tracking | Track delivery | OrderShipped, DeliveryConfirmed | UI, Notifications |

---

## Detailed Read Models

### ReadModel: OrderStatusView

**Purpose**: Order Status screen displays current order state

**Events subscribed**:
- OrderCreated
- OrderConfirmed
- PaymentAuthorized
- OrderShipped
- OrderCancelled
- DeliveryConfirmed

**Data**:

{ orderId: string customerId: string status: 'Draft' | 'Confirmed' | 'Authorized' | 'Shipped' | 'Delivered' | 'Cancelled' items: Array<{productId, quantity, unitPrice}> total: number shippingAddress: Address

createdAt: Date confirmedAt: Date paymentId: string paymentMethod: 'card' | 'transfer' authorizedAt: Date

shipmentId: string carrier: string trackingNumber: string shippedAt: Date estimatedDelivery: Date }


**Update Logic**:
- OrderCreated: Insert with status='Draft'
- OrderConfirmed: Update status='Confirmed'
- PaymentAuthorized: Update status='Authorized', set paymentId
- OrderShipped: Update status='Shipped', set shipmentId, carrier, trackingNumber
- DeliveryConfirmed: Update status='Delivered'
- OrderCancelled: Update status='Cancelled'

**Consumed By**:
- Order Status Screen (displays)
- Order Processing Processor (checks status)
- Notification System (sends updates)

--- [Repeat for each read model]

---

## Data Completeness Check

### Events → UI Needs

Verify all UI needs have event sources:

| UI Need | Event Source | Status |
|---------|-------------|--------|
| Order status | OrderConfirmed, OrderShipped |  |
| Tracking number | OrderShipped |  |
| Order items | OrderCreated |  |
| Estimated delivery | OrderShipped |  |
| Cancellation reason | OrderCancelled |  |

### Missing Data

Identify UI needs without event sources:
- None identified 

---

## Processor Consumption

### Processors and their reads:

| Processor | Reads From | Writes Commands |
|-----------|-----------|-----------------|
| Inventory | OrderStatusView (Authorized) | ReserveInventory |
| Fulfillment | OrderStatusView (InventoryReserved) | CreateShipment |
| Notification | OrderStatusView (all) | None (info-only) |
| Accounting | PaymentStatusView | None (reporting) |

Quality Checklist

Read Model Design

  • Every UI screen maps to read model(s)
  • Every read model has clear purpose
  • Every data field has event source
  • Update logic for each event is explicit
  • All UI needs are covered
  • Processor reads are identified
  • Read model access patterns clear
  • No undocumented data sources
  • Compensation/cancellation handled
  • Error states shown

CRITICAL: Event vs Read Model Validation

  • Reviewed each read model: "Is this pure calculation or an actual domain fact?"
  • No aggregations modeled as events: (totals, averages, counts are read models)
  • No recalculated state modeled as events: (if value changes multiple times, it's a read model)
  • Processor outputs are categorized:
    • Produces NEW EVENT = actual domain fact (e.g., PaymentAuthorized)
    • Updates READ MODEL = calculation (e.g., SellerRatingCalculated)
    • Sends NOTIFICATION = info-only (no event or model)
  • History tracking is clear: Derived state keeps history in read model history[], not as separate events

Key Principles

  1. Event-Driven: All data comes from events
  2. Projection-Based: Read models are projections, not persistent
  3. UI-Focused: Optimized for UI display needs
  4. Processor-Friendly: Enough data for processor decisions
  5. Completeness: All needed data available

Common Patterns

Status View Pattern

Events: Create, Confirm, Process, Ship
ReadModel: Accumulates data from all events
Displayed: Current state reflecting all events

List View Pattern

Events: Create, Update, Delete (Cancel)
ReadModel: Summary of each item
Used for: Filtering, sorting, searching

Timeline View Pattern

Events: Any event with timestamp
ReadModel: Chronological list
Used for: History, audit trail

Processor Decision Pattern

Events: State-changing events
ReadModel: Current state only
Processor reads to decide next action
Related skills
Installs
19
First Seen
Apr 13, 2026