Skip to main content
Engineering/boris-model

Boris Model

You need to model service relationships from Event Storming outputs.

Use this when you have Event Storming outputs (domain events, bounded contexts, thin slices) and need to translate them into a notional system architecture showing how services interact. Boris focuses on relationships between components, not implementation details.

Full guide: See the Architecture Discovery Workshop Playbook for the complete method.

Framework attribution: BORIS modeling builds on Alberto Brandolini's EventStorming method for collaborative domain discovery.

Process

Step 1: Gather inputs

Ask the user:

  1. Event Storming outputs — domain events, bounded contexts, thin slices, and hot spots. If running as part of /architecture-discovery, these carry forward automatically.
  2. Target architecture style — microservices, modular monolith, event-driven, or undecided?
  3. Happy path to model first — which thin slice should we walk through?
  4. Session format — collaborative workshop or async documentation?
  5. Output destination — conversation only, Miro, FigJam, Notion, or file?

Step 2: Set up the Boris board

For each bounded context from Event Storming, create a service node:

  1. Service name — use the bounded context name
  2. Key responsibilities — what this service owns (2-3 bullet points)
  3. Domain events it produces — events this service publishes
  4. Domain events it consumes — events this service subscribes to

Arrange services spatially — related services near each other, external systems at the edges.

Step 3: Walk the happy path

Take the first thin slice and trace it through the Boris diagram:

  1. Start with the triggering actor/command
  2. For each step, identify:
    • Which service handles it?
    • Is the interaction synchronous (API call, request/response) or asynchronous (event, publish/subscribe)?
    • What data flows between services?
  3. Draw arrows between services with labels:
    • Solid arrows → synchronous (API calls)
    • Dashed arrows → asynchronous (events)
  4. Note where the flow crosses bounded context boundaries

Step 4: Walk additional flows

Repeat Step 3 for each thin slice:

  • Alternate paths
  • Exception paths
  • Error/compensation flows

Use different colors or annotations to distinguish flows on the same diagram.

Step 5: Identify emerging patterns

As flows accumulate, call out:

  • Orchestration vs. choreography — is there a central coordinator or do services react to events independently?
  • Shared data concerns — where do two services need the same data?
  • Chatty interactions — where are there too many synchronous calls?
  • Single points of failure — which service appears in every flow?
  • API candidates — which service-to-service interactions imply formal APIs?

Step 6: Generate the Boris model output

Use the Boris template to produce the deliverable:


Boris Model: (Domain/System Name)

Date: (date) Source: Event Storming session (date) Architecture style: (microservices / modular monolith / event-driven / hybrid)

Services

ServiceResponsibilitiesProduces EventsConsumes Events
(Service name)(Key responsibilities)(Events published)(Events consumed)

Interactions

FromToTypeDescriptionFlow
(Service A)(Service B)Sync/Async(What's communicated)(Which thin slice)

Flow: (Happy Path Name)

Actor → [Service A] --sync--> [Service B] --async--> [Service C]
         (command)    (API)     (event)      (sub)     (result)

Flow: (Alternate Path Name)

(Same format for each additional flow)

Patterns Identified

PatternWhereImplication
(e.g., Choreography)(Which services)(What this means for implementation)

API Candidates

APIProviderConsumer(s)Purpose
(API name)(Service)(Services)(What it does)

Open Questions

  • (Question about service boundaries)
  • (Question about data ownership)
  • (Question about communication patterns)

Step 7: Share and distribute (optional)

If the user requested a tool destination:

  • Miro — use doc_create for the model document, or diagram_create for a visual service diagram
  • FigJam — use generate_diagram to create a flowchart showing service interactions
  • Notion — use notion-create-pages with the output as content
  • File — save to ./output/boris/(domain-name)-boris.md

Step 8: Review

Ask the user:

  • Do the service boundaries feel right?
  • Are there services that should be split or merged?
  • Which synchronous calls should actually be asynchronous?
  • Are we ready to move to SNAP documentation, or do we need another modeling pass?

Uncertainty Policy

TopicToleranceAction
Service boundaries and ownershipLowSTOP and ask — wrong boundaries create wrong architecture
Data flow direction between servicesLowSTOP and ask — sync vs. async affects system design fundamentally
Interaction type (command/query/event)MediumAssume + flag [ASSUMED] — team validates during flow walkthroughs
Service namingMediumAssume + flag [ASSUMED] — names evolve, consistency matters more
Shared data ownership resolutionMediumAssume + flag [ASSUMED] — flag as design decision for team
Non-functional requirements per serviceHighBest guess — SNAP documentation captures details

Default: STOP and ask when a topic is not listed above.

Related skills

  • /event-storm — previous stage: discover domain events and bounded contexts
  • /snap-document — next stage: document APIs, data, and risks per bounded context
  • /architecture-discovery — skill set that chains all three stages

Output locations

FormatLocationNotes
MarkdownConversation or saved fileAlways produced. Source of truth.
Miro doc/diagramUser's specified Miro boardVia Miro MCP.
FigJam diagramFigJamService interaction flowchart.
Notion pageUser's specified Notion locationVia Notion MCP.
File./output/boris/(domain)-boris.mdLocal file.

Example Output

Input

  • Event Storming outputs: Domain events: OrderPlaced, PaymentAuthorized, PaymentDeclined, InventoryReserved, InventoryShortfall, OrderFulfilled, ShipmentCreated, CustomerNotified. Bounded contexts: Order Management, Payment Processing, Inventory, Fulfillment, Notifications. Thin slices: (1) Happy path — customer places order through delivery confirmation, (2) Payment declined — order cancelled and customer notified.
  • Company / domain: Meridian Supply Co. — B2B wholesale e-commerce platform
  • Target architecture style: Event-driven microservices
  • Happy path to model first: Thin Slice 1 — successful order through shipment
  • Output destination: Conversation + save to file

Output (abbreviated)

Boris Model: Meridian Supply Co. — Order Fulfillment Domain

Date: 2025-07-14 Source: Event Storming session — 2025-07-11 Architecture style: Event-driven microservices


Services

ServiceResponsibilitiesProduces EventsConsumes Events
Order ManagementAccepts and validates orders; owns order lifecycle stateOrderPlaced, OrderCancelledPaymentAuthorized, PaymentDeclined, InventoryShortfall, OrderFulfilled
Payment ProcessingCharges accounts; manages payment retries and declinesPaymentAuthorized, PaymentDeclinedOrderPlaced
InventoryTracks stock levels; reserves and releases inventoryInventoryReserved, InventoryShortfallOrderPlaced, OrderCancelled
FulfillmentPicks, packs, and ships orders; owns shipment recordsShipmentCreated, OrderFulfilledInventoryReserved, PaymentAuthorized
NotificationsSends transactional emails/SMS to buyers and repsCustomerNotifiedOrderPlaced, PaymentDeclined, ShipmentCreated, OrderCancelled

Interactions

FromToTypeDescriptionFlow
Buyer PortalOrder ManagementSyncSubmit order command, receive order IDSlice 1 & 2
Order ManagementPayment ProcessingAsyncPublishes OrderPlaced; Payment subscribesSlice 1 & 2
Order ManagementInventoryAsyncPublishes OrderPlaced; Inventory subscribesSlice 1 & 2
Payment ProcessingOrder ManagementAsyncPublishes PaymentAuthorized or PaymentDeclinedSlice 1 & 2
InventoryOrder ManagementAsyncPublishes InventoryReserved or InventoryShortfallSlice 1 & 2
FulfillmentOrder ManagementAsyncPublishes OrderFulfilled after shipmentSlice 1
FulfillmentNotificationsAsyncShipmentCreated triggers customer notificationSlice 1
Order ManagementNotificationsAsyncOrderCancelled triggers decline notificationSlice 2
Buyer PortalOrder ManagementSyncQuery order status (polling)Slice 1 & 2

Flow: Happy Path — Successful Order Through Shipment

Buyer --sync--> [Order Management] --async(OrderPlaced)--> [Payment Processing]
                                   --async(OrderPlaced)--> [Inventory]

[Payment Processing] --async(PaymentAuthorized)--> [Order Management]
[Inventory]          --async(InventoryReserved) --> [Order Management]

[Order Management] (both confirmations received — internal state: READY)

[Fulfillment] <--async(InventoryReserved + PaymentAuthorized)--
[Fulfillment] --async(ShipmentCreated)--> [Notifications] --> CustomerNotified
[Fulfillment] --async(OrderFulfilled) --> [Order Management] → state: COMPLETE

Flow: Payment Declined — Order Cancelled

Buyer --sync--> [Order Management] --async(OrderPlaced)--> [Payment Processing]

[Payment Processing] --async(PaymentDeclined)--> [Order Management]
[Order Management] → state: CANCELLED
[Order Management] --async(OrderCancelled)--> [Inventory]   (release reservation)
[Order Management] --async(OrderCancelled)--> [Notifications] --> CustomerNotified

Patterns Identified

PatternWhereImplication
ChoreographyPayment → Order Mgmt → Fulfillment chainNo central orchestrator; resilient but harder to trace. Consider adding correlation IDs and a saga log.
Parallel fan-outOrder Management triggers Payment and Inventory simultaneouslyBoth must succeed before Fulfillment proceeds — need a join/aggregation mechanism in Order Management.
Single point of failureOrder Management appears in every flowPrioritize high availability and idempotent event handling for this service first.
Chatty sync boundaryBuyer Portal polling Order Management for statusEvaluate WebSocket or server-sent events to reduce polling load at scale.

API Candidates

APIProviderConsumer(s)Purpose
Order Submission APIOrder ManagementBuyer PortalPlace and validate new orders; return order ID
Order Status APIOrder ManagementBuyer Portal, Internal Ops UIQuery current order state and history
Payment Authorization APIPayment Processing(internal — event-driven, no direct call)[ASSUMED] May be needed for synchronous retry UI
Inventory Availability APIInventoryBuyer PortalCheck stock levels before order submission

Open Questions

  • Join logic ownership: Should Order Management maintain a saga/state machine waiting for both PaymentAuthorized and InventoryReserved, or should Fulfillment own the join? This affects where compensation logic lives.
  • Partial inventory: What happens when Inventory can fulfill part of an order? InventoryShortfall is modeled as a full block — partial fulfillment rules are undefined.
  • Payment retry: Is a declined payment terminal, or can the buyer retry? If retryable, Order Management's state model needs a PENDING_PAYMENT state with a timeout.
  • Notification ownership: Notifications currently subscribes to raw domain events. Should Order Management publish a richer OrderStatusChanged event instead, to decouple Notifications from other services' event schemas?

Saved to ./output/boris/meridian-supply-order-fulfillment-boris.md