Application and Process Layer
The original “blue book” (by Evans) as well as the “red book” (by Vernon) talk about domain and application layers. Although Context Mapper is focused on strategic DDD and domain models (domain layer) inside Bounded Contexts, we support modeling services, commands, and events in an application layer. With the application layer concept in CML users have the possibility to declare which services and/or commands can be called/triggered from outside a Bounded Context and which events are published to the outside.
In addition to that, the application layer offers a basic syntax to model processes or event/command flows. This can, for example, be helpful to bring events and commands (maybe outcome of an Event Storming) into a timeline and explicitly state which event are emitted by which commands (or service operations) and which commands are triggered by events.
The application layer also provides an alternative way to model processes without relying on event/command syntax. This is achieved by defining a coordination between service operations.
Application Services and Commands
DDD practitioners and experts typically distinguish between domain services and application services. Therefore, CML offers the possibility to use the service construct both inside Aggregates (domain service) and inside the application layer (application service). Note that the grammar/syntax for application services is exactly the same as for the services inside the Aggregates.
The following example illustrates how to model an application layer in a Bounded Context and add application services:
BoundedContext ClaimsManagement {
Application {
Service ClaimsApplicationService {
void submitClaim(@Claim claim);
void checkInsurance(@Claim claim);
void acceptClaim(@Claim claim);
void rejectClaim(@Claim claim);
}
}
Aggregate Claims {
Entity Claim {
aggregateRoot
long claimId
CustomerId customer
String description
Blob requestDocument
boolean isComplete
boolean isAssessed
- ClaimState claimState
}
enum ClaimState {
aggregateLifecycle
OPEN, REJECTED, ACCEPTED
}
abstract DomainEvent ClaimEvent {
long claimId
Date timestamp
}
DomainEvent ClaimSubmitted extends ClaimEvent
DomainEvent ClaimAccepted extends ClaimEvent
DomainEvent ClaimRejected extends ClaimEvent
}
}
Optionally, it is possible to name the application layer:
Application ClaimsApplicationLayer {
Service ClaimsApplicationService {
void submitClaim(@Claim claim);
void checkInsurance(@Claim claim);
void acceptClaim(@Claim claim);
void rejectClaim(@Claim claim);
}
}
As documented on the Aggregate page, it is also possible to specify state transitions made by operations. Please consult the Aggregate documentation for the syntax of state transitions. The same syntax is available for the operations of application services:
Application {
Service ClaimsApplicationService {
void submitClaim(@Claim claim) : write [ -> OPEN];
void checkInsurance(@Claim claim);
void acceptClaim(@Claim claim) : write [ OPEN -> ACCEPTED ];
void rejectClaim(@Claim claim) : write [ OPEN -> REJECTED ];
}
}
Hint: If you model processes/flows (see documentation of syntax below) you may want to declare the state transitions in the flow steps instead of service operations.
As an alternative to “service operations” you can also create semantically equivalent commands and events in the application layer; maybe you work with Event Sourcing and/or CQRS, and these terms fit better.
The syntax for domain events and commands is documented on the tactic DDD reference page. The following example illustrates how you can use those concepts in the application layer as well:
Application {
Command SubmitClaim {
// attributes/body optional
}
Command AcceptClaim
Command RejectClaim
Event ClaimSubmitted {
// attributes/body optional
}
Event ClaimAccepted
Event ClaimRejected
}
Hint: Modeling commands that are triggered by users or external systems in the application layer may feel natural. Events, however, may be part of your domain model. Therefore you should decide consciously whether you want to model these events on the application layer or inside the corresponding domain layer Aggregate.
Processes and Event/Command Flows
In addition to application services and commands, the application layer offers language features to model event/command flows. Events typically occur in a certain order over time. For example in Event Stormings Events are typically ordered by the timeline from left to right. An example: (from our Event Storming tutorial)
Our flow grammar supports Context Mapper users in modeling such event/command flows in CML. The following example models the event storming result illustrated above:
Application {
/* we removed commands and events here to keep the sample shorter */
Flow ClaimsFlow {
command SubmitClaim emits event ClaimSubmitted
event ClaimSubmitted triggers command CheckClaimDocumentation
command CheckClaimDocumentation emits event ClaimRegistered
event ClaimRegistered triggers command CheckInsurance
command CheckInsurance emits event AssessmentPerformed
event AssessmentPerformed triggers command AcceptClaim X RejectClaim
command AcceptClaim delegates to Claims [OPEN -> ACCEPTED] emits event ClaimAccepted
command RejectClaim delegates to Claims [OPEN -> REJECTED] emits event ClaimRejected
event ClaimAccepted triggers command SchedulePayment
command SchedulePayment emits event PaymentPerformed
event PaymentPerformed triggers command NofifyCustomer
event ClaimRejected triggers command NofifyCustomer
command NofifyCustomer delegates to Claims [ACCEPTED, REJECTED -> CUSTOMER_NOTIFIED] emits event CustomerNotified
}
}
The example above works with commands. It is however possible to model the same flow with service operations:
Application {
Service ClaimsApplicationService {
void submitClaim(@Claim claim);
void checkClaimDocumentation(@Claim claim);
void checkInsurance(@Claim claim);
void acceptClaim(@Claim claim);
void rejectClaim(@Claim claim);
void schedulePayment(@Claim claim);
void nofifyCustomer(@Claim claim);
}
Flow ClaimsFlow {
operation submitClaim emits event ClaimSubmitted
event ClaimSubmitted triggers operation checkClaimDocumentation
operation checkClaimDocumentation emits event ClaimRegistered
event ClaimRegistered triggers operation checkInsurance
operation checkInsurance emits event AssessmentPerformed
event AssessmentPerformed triggers operation acceptClaim X rejectClaim
operation acceptClaim delegates to Claims [OPEN -> ACCEPTED] emits event ClaimAccepted
operation rejectClaim delegates to Claims [OPEN -> REJECTED] emits event ClaimRejected
event ClaimAccepted triggers operation schedulePayment
operation schedulePayment emits event PaymentPerformed
event PaymentPerformed triggers operation nofifyCustomer
event ClaimRejected triggers operation nofifyCustomer
operation nofifyCustomer delegates to Claims[ACCEPTED, REJECTED -> CUSTOMER_NOTIFIED] emits event CustomerNotified
}
}
In the following we explain the grammar in detail and step by step…
Flow Grammar
There are basically two types of flow steps that are supported by CML:
- Type 1: an event triggers an operation/command (command/operation invocation)
- Type 2: a command/operation emits an event (event production)
These types of steps are inspired by the Event Storming Cheat sheet of the DDD crew, the following illustration in particular:
Note: We do not support all of the concepts illustrated above! Especially policies and the “query model / information” sticky are not supported explicitly.
The picture above illustrates the two main step types mentioned above.
- Type 1: an event triggers (the DDD crew uses the term issues) a command (in CML without the policy in between)
- Type 2: a command (CML: or operation) invoked on an Aggregate emits (the DDD crew uses the term produces) an event
In the following we will see that we also support the Aggregate in Type 2; the actor that can trigger a command/operation.
The following example illustrates the basic syntax of the two step types:
Application {
/* we removed commands and events here to keep the sample shorter */
Flow ClaimsFlow {
/* type 2: (event production) */
command SubmitClaim emits event ClaimSubmitted
/* type 2 alternative: (event production) */
operation submitClaim emits event ClaimSubmitted
/* type 1: (command invokation) */
event ClaimSubmitted triggers command CheckClaimDocumentation
/* type 1 alternative: (operation invokation) */
event ClaimSubmitted triggers operation checkClaimDocumentation
}
}
Emitting Events
A command or operation can emit multiple events. This is expressed with the + sign as follows:
command SubmitClaim emits event Event1 + Event2 + Event3 // and so on... (emit as many events as you want)
// alternative
operation submitClaim emits event Event1 + Event2
The example above illustrates that an operation emits multiple events events, which means, all events are emitted!
There are situations in which you want to express that one OR an other event is emitted. In this case, we distinguish between exclusive OR (XOR: only one of the listed events is emitted) and inclusive OR (multiple events can be thrown, but not necessarily all). For these two cases we use the symbols X and O, inspired by BPMN.
Exclusive variant:
command SubmitClaim emits event Event1 X Event2 X Event3
// alternative
operation submitClaim emits event Event1 X Event2
Inclusive variant:
command SubmitClaim emits event Event1 O Event2 O Event3
// alternative
operation submitClaim emits event Event1 O Event2
Symbol Summary:
The following table summarizes the three symbols that are supported here:
Symbol | Meaning | Corresponding BPMN gateways (in German) |
---|---|---|
+ | ALL listed events are emitted. | Parallel Gateway |
X or x | ONLY ONE of the listed events is emitted. | Exclusive Gateway |
O or o | ONE OR MULTIPLE, BUT NOT NECESSARILY ALL of the listed events are emitted. | Inclusive Gateway |
Note: It is not possible to mix the symbols inside a single flow step (we do not implement operator precedence).
Command/Operation triggered by multiple Events
On the other hand a command or operation may only be triggered if multiple events happen. This is modelled as follows:
event ClaimSubmitted + ContractChecked triggers command AssessClaim
// alternative
event ClaimSubmitted + ContractChecked triggers operation assessClaim
This example expresses that both events (ClaimSubmitted as well as ContractChecked) must have been emitted so that AssessClaim is triggered.
Note: At this point only the + symbol is supported.
Triggering Commands/Operations
Similar to the event productions explained above, it is possible that events trigger multiple commands/operations. This syntax works with the symbols +, X, and O again.
Parallel variant:
event ClaimSubmitted triggers command CheckClaim + CheckPolicy
// operation alternative:
event ClaimSubmitted triggers operation checkClaim + checkPolicy
This example expresses that all listed operations/commands are triggered (parallel).
Exclusive variant:
event ClaimSubmitted triggers command CheckClaim X CheckPolicy
// operation alternative:
event ClaimSubmitted triggers operation checkClaim X checkPolicy
This example means that only one of the commands/operations is triggered.
Inclusive variant:
event ClaimSubmitted triggers command CheckClaim O CheckPolicy
// operation alternative:
event ClaimSubmitted triggers operation checkClaim O checkPolicy
This example expresses that one or multiple but not necessarily all commands/operations are triggered.
Symbol summary:
The following table summarizes the meaning of the supported symbols in the command/operation invokation steps:
Symbol | Meaning | Corresponding BPMN gateways |
---|---|---|
+ | ALL listed commands/operations are triggered. | Parallel Gateway |
X or x | ONLY ONE of the listed commands/operations is triggered. | Exclusive Gateway |
O or o | ONE OR MULTIPLE, BUT NOT NECESSARILY ALL of the listed commands/operations are triggered. | Inclusive Gateway |
Commands/Operations delegating to Aggregates
Commands and operations are typically delegated to an Aggregate. This also corresponds to the illustration of the DDD crew depicted above.
In CML this means that you can optionally add the following reference to event production steps (Type 2 above):
command SubmitClaim delegates to Claims emits event Event1 X Event2 X Event3
// alternative
operation submitClaim delegates to Claims emits event Event1 X Event2
Note that this is a reference to an Aggregate that must actually exist in your Bounded Context. In the example above there must be an Aggregate called Claims in the same Bounded Context.
State Transitions
In case your flow step delegates to an Aggregate (contains part delegates to) as in the example above, it is possible to declare the state transition that is caused right here.
Note: State transition can be modelled on service and domain object operations as documented on the Aggregate doc page.
If you use flows you can add the state transitions to the flow steps instead of modeling them inside the Aggregate (operations). The state transitions can be added to the event production steps (type 2 above) as follows (optionally):
command SubmitClaim delegates to Claims [ -> SUBMITTED ] emits event Event1 X Event2 X Event3
// alternative
operation submitClaim delegates to Claims [ -> SUBMITTED ] emits event Event1 X Event2
For a complete documentation and examples for the state transition grammar we refer to the Aggregate page.
Actors
Commands or operations can be triggered by actors/users. In CML you can specify this (optionally) as follows:
command SubmitClaim [ initiated by "Customer" ] delegates to Claims [ -> SUBMITTED ] emits event Event1
// alternative
operation submitClaim [ initiated by "Customer" ] delegates to Claims [ -> SUBMITTED ] emits event Event1
Visualization with BPMN Sketch Miner
Once you modelled an application flow you can visualize it in the BPMN Sketch Miner. Use Context Mapper’s BPMN Sketch Miner generator to generate the Sketch Miner DSL output or directly open the visualization in the browser.
An example output: (visualization powered by BPMN Sketch Miner)
Note: This is a process we took (modeled) from the Lakeside Mutual project.
Coordination between Application Services
In case you want to model processes/workflows that span multiple Bounded Contexts without the use of event/command syntax, you can also do so by defining a coordination between application services. This language feature is based on the concept of Coordination from the “Software Architecture: The Hard Parts” book by Neal Ford and others. They define Coordination as a property of workflows, which can either be orchestrated (the workflow steps are coordinated by a central component) or choreographed (each step of the workflow shares coordination logic).
To model these workflows in Context Mapper, you use the coordination construct inside the application layer of a Bounded Context. The following example illustrates this using an adaptation of the same claims processing concept from before:
ContextMap {
contains ClaimsManagement
contains InsuranceManagement
contains PaymentManagement
ClaimsManagement <-> InsuranceManagement
ClaimsManagement <-> PaymentManagement
}
BoundedContext ClaimsManagement {
Application {
Coordination SubmitValidClaimCoordination {
ClaimsManagement::ClaimsApplicationService::submitClaim;
InsuranceManagement::InsuranceApplicationService::checkInsurance;
ClaimsManagement::ClaimsApplicationService::acceptClaim;
PaymentManagement::PaymentApplicationService::performPayment;
}
Service ClaimsApplicationService {
void submitClaim(@Claim claim);
void acceptClaim(@Claim claim);
}
}
}
BoundedContext InsuranceManagement {
Application {
Service InsuranceApplicationService {
void checkInsurance(@Claim claim);
}
}
}
BoundedContext PaymentManagement {
Application {
Service PaymentApplicationService {
void performPayment(@Claim claim);
}
}
}
Note: The type of workflow coordination (orchestration or choreography) is not explicitly supported in the syntax, but can still be modeled by assuming that the Bounded Context where the coordination is defined serves as either the orchestrator, or the start of the choreography.
Coordination Grammar
In CML, coordinations are composed of coordination steps. Each coordination step represents a call to an application service operation, which can be defined inside the same Bounded Context or in an outer Bounded Context.
To correctly reference an application service operation, a coordination step is divided into three segments, separated by the ::
symbol:
- The name of the Bounded Context where the operation is defined;
- The name of the application service where the operation is defined;
- And the name of the operation.
The following is an example of a coordination step:
// Bounded context name :: application service name :: service operation name
ClaimsManagement::ClaimsApplicationService::submitClaim;
Each of the three segments of a coordination step is also subject to certain syntax rules to maintain the integrity of the model. Respectively, these are:
- A coordination step can only reference outer Bounded Contexts that are upstream (by means of an upstream/downstream relationship or a symmetrical relationship in a Context Map definition);
- A coordination step can only reference services defined in the application layer of a Bounded Context (which means domain services defined inside aggregates are not applicable);
- A reference to a service operation in a coordination step should be unique within the service.
Coordination visualization in BPMN
Like flows, coordinations can also be visualized with BPMN Sketch Miner. The following example shows the output when using Context Mapper’s BPMN Sketch Miner generator on coordinations:
- Previous
- Next