title | permalink | image |
---|---|---|
Model Event Storming Results in Context Mapper |
/docs/event-storming/ |
/img/cm-og-image.png |
Event storming is a workshop technique to explore domains originally invented by Alberto Brandolini. If you are not familiar with the technique we recommend the following literature and links:
- Introducing Event Storming by Alberto Brandolini (original blog post)
- Introducing Event Storming by Alberto Brandolini (Leanpub book)
- Domain-Driven Design Distilled by Vaughn Vernon. (Chapter 7 introduces Event Storming as an acceleration and management tool for DDD)
- Event Storming Cheatsheet by Wolfgang Werner (good cheat sheet for a quick introduction into the topic)
The output of an event storming describes the domain using the following DDD concepts:
- Domain Events
- Commands (that cause the Domain Events)
- Aggregates (or Aggregate root Entities)
- Issues (optional)
- User Roles (optional)
- Views / Read Models (optional)
- Bounded Contexts
- Subdomains
- Event Flows (optional)
Note: Some event stormers also introduce policies (a.k.a. business rules) as special, self-triggered types of commands.
Context Mapper supports most of these concepts, and can therefore be used to document the output of an event storming workshop. Once captured in Context Mapper, the workshop results can be processed further, for instance to generate UML diagrams or service contracts.
In the following we present an example Event Storming and how the result can be modeled in CML.
The Lakeside Mutual project is a fictitious insurance application that illustrates microservices and the application of Microservice API Patterns (MAP). The application does currently not support claim processing. To add this as a new feature to the application we conducted an Event Storming.
The following graphic illustrates the result of the Event Storming: (click on image to enlarge)
The next sections will illustrate how we suggest to model the individual results of the Event Storming in CML. The complete CML model resulting from this Event Storming can be found here: https://github.com/ContextMapper/context-mapper-examples/tree/master/src/main/cml/lakeside-mutual.
The following examples and modeling suggestions are based on the Event Storming presented above (Lakeside Mutual).
The domain events are typically the first objects that are discovered in an Event Storming. The tactic DDD syntax of CML is based on the Sculptor DSL, which supports event-driven concepts. Hence, you can model DomainEvents within your Aggregates. The following examples illustrate how we modeled the domain events from our Event Storming for Lakeside Mutual:
abstract DomainEvent AbstractClaimEvent {
Date timestamp
- Claim claim
}
DomainEvent ClaimRegistered extends @AbstractClaimEvent // triggers CheckInsurance command
DomainEvent AssessmentPerformed extends @AbstractClaimEvent // triggers AcceptClaim or RejectClaim command
DomainEvent ClaimAccepted extends @AbstractClaimEvent // triggers SchedulePayment command
DomainEvent ClaimRejected extends @AbstractClaimEvent // triggers NofifyCustomer command
Note: In the Event Sourcing and CQRS Modeling in Context Mapper page we describe how event sourcing and CQRS based systems can be modeled in Context Mapper.
Domain events often result from a user action or command execution. Commands in CML can either be modeled as methods in services or as CommandEvent's (or both). The following examples illustrate how you can model commands in CML:
abstract CommandEvent AbstractClaimCommand {
- Claim claim
}
"role: Administrator in charge"
CommandEvent CheckClaimDocumentation extends @AbstractClaimCommand
"role: Responsible person in claims department"
CommandEvent CheckInsurance extends @AbstractClaimCommand {
- Set<PolicyId> policies
}
"role: Responsible person in claims department"
CommandEvent AcceptClaim extends @AbstractClaimCommand
"role: Responsible person in claims department"
CommandEvent RejectClaim extends @AbstractClaimCommand
Service ClaimService {
@ClaimRegistered checkDocumentation(@CheckClaimDocumentation command);
@AssessmentPerformed checkInsurance(@CheckInsurance command);
@ClaimAccepted acceptClaim(@AcceptClaim command);
@ClaimRejected rejectClaim(@RejectClaim commandcal);
}
Each command is represented as an operation; we grouped them by Aggregates (see below) in this case. @CheckClaimDocumentation
represents the command input, which we modelled as a
command event. Alternatively, a value object or an entity could have been defined. The return type of the service operations indicate that the result of a command is a certain event.
For example: If the CheckClaimDocumentation
command is performed, an event ClaimRegistered
will be the result.
Policies can be modeled in the same way, and optionally their if-then rule character can be modeled explicitly. For example:
abstract DomainEvent AbstractPolicy
DomainEvent CustomerNotificationPolicy // triggers CustomerNotified event
Aggregates are supported by CML and can be modeled within Bounded Contexts. The syntax is documented here. Optionally, you may want to add entities to them.
The following example illustrates the Notification
Aggregate derived from our Event Storming example above:
Aggregate Notification {
Entity Notification {
aggregateRoot
- Claim claim
}
CommandEvent NofifyCustomer
DomainEvent CustomerNotified {
Date timestamp
}
Service NotificationService {
@CustomerNotified notifyCustomer(@NofifyCustomer command);
}
}
Note: Some Event Storming tutorials/guides also feature Entities instead of Aggregates. Typically these Entities become Aggregate roots (often the Aggregate even has the same name as the Aggregate root Entity). In CML, it does not really matter whether you work with Aggregates or Entities in your Event Storming model: You have to create an Aggregate in all cases. Within this Aggregate you can then create your Entity, as shown in the example above.
At present, we do not have a language construct for issues since they mostly only used as notes for potential future model changes and are not further processed (do not influence the model structurally). However, you can capture them as comments, although it might make more sense to capture them in the issue tracking tool or Kanban board of the project straight away.
The Lakeside Mutual Event Storming output above contains two issues (the two red cards in the Event Storming above) for which we created the following comment in our CML model:
/**
* The 'Claims Management' context below is a result of our Event Storming for the new
* claim processing feature.
*
* Issue: We currently modeled the feature within a new Bounded Context, although it
* would also be possible to implement it as part of the Policy Management Context.
*/
BoundedContext ClaimsManagement {
// Bounded Context content removed for this example ...
}
At present, there is no concept of a user role in the Context Mapper DSL; however, we used Sculptor's doc
comment which can be added to
all domain objects. Thereby we declare the user roles on our commands:
"role: Administrator in charge"
CommandEvent CheckClaimDocumentation extends @AbstractClaimCommand
"role: Responsible person in claims department"
CommandEvent CheckInsurance extends @AbstractClaimCommand {
- Set<PolicyId> policies
}
"role: Responsible person in claims department"
CommandEvent AcceptClaim extends @AbstractClaimCommand
"role: Responsible person in claims department"
CommandEvent RejectClaim extends @AbstractClaimCommand
In our Event Storming for claims processing at Lakeside Mutual we did not work with read models; Context Mapper does not support read models explicitly yet. However, it is of course possible that you define your read model simply by using separate Aggregates or Entities. For example:
Aggregate ClaimReadModel {
Entity ClaimReadModel {
String claimIdentifier
...
}
}
Bounded Contexts are first class citizens in CML. Their syntax in documented here. The example below (subdomains) illustrates how the Bounded Context for the Lakeside Mutual claim processing has been modeled.
Just like Bounded Contexts, Subdomains are root objects and first class citizens in CML. The Subdomain syntax is documented here. The following CML example illustrates how you can define Subdomains and how Bounded Contexts can implement those Subdomains:
Domain InsuranceDomain {
Subdomain ClaimProcessing {
type CORE_DOMAIN
}
}
BoundedContext ClaimsManagement implements ClaimProcessing {
type FEATURE
domainVisionStatement "This Bounded Context manages the processing of claims ..."
Aggregate ClaimSelfService { /* removed content for this example ... */ }
Aggregate Claims { /* removed content for this example ... */ }
Aggregate Payment { /* removed content for this example ... */ }
Aggregate Notification { /* removed content for this example ... */ }
}
In an Event Storming you order the events in the order they occur (in time). We did the same thing in the Event Storming above (Lakeside Mutual). The time line in the illustrated graphic above proceeds from left to right.
Context Mapper does not provide any support for modeling this event flow (time) explicitly yet. For this reason we simply worked with comments to indicate which command is triggered after an event has been emitted:
"role: Administrator in charge"
CommandEvent CheckClaimDocumentation extends @AbstractClaimCommand
DomainEvent ClaimRegistered extends @AbstractClaimEvent // triggers CheckInsurance command
"role: Responsible person in claims department"
CommandEvent CheckInsurance extends @AbstractClaimCommand {
- Set<PolicyId> policies
}
DomainEvent AssessmentPerformed extends @AbstractClaimEvent // triggers AcceptClaim or RejectClaim command
"role: Responsible person in claims department"
CommandEvent AcceptClaim extends @AbstractClaimCommand
DomainEvent ClaimAccepted extends @AbstractClaimEvent // triggers SchedulePayment command
"role: Responsible person in claims department"
CommandEvent RejectClaim extends @AbstractClaimCommand
DomainEvent ClaimRejected extends @AbstractClaimEvent // triggers NofifyCustomer command
The order in which we listed the events/commands above and the comments // triggers ...
indicate the time line in this example.
Once your CML that models the Event Storming output validates, you can:
- Refine it, for instance by defining the data (attributes, operation parameters and return types) in more detail.
- Checkout the tactic DDD syntax and the Sculptor documentation.
- Refactor it, starting with use cases and team assignments (AR-2, AR-3, etc.).
- Generate output from it, for instance a domain glossary, a graphical Context Map, PlantUML diagrams, or MDSL contracts.