This entire system revolves around "messages". A message can map into a few concepts:
An event or message has the form $msg subject.verb (parm1=value,...)
. It associates a verb to a subject and a list of attributes.
The subject can be qualified and can be:
email
or bell
Person
or product
my.system.email
- use this for packages or other grouping or hierarchy of entitiesThe verb is a single word (not qualified) can be:
Person.walked_in
email.create
bell.ring
Messages can be received, generated, sent or mocked:
$when
$msg
and a known protocol$mock
$expect
Messages can and should be declared, inside a Spec like so:
$msg home.guest_arrived(name:String ~= "Jane") : (greetings:String)
The type annotations and default values for arguments are optional - they are helpful though and it's good practice to provide them!
If messages are not declared, their respective rules are still applied, but there is less information for the engine or the integration points (receiving messages) to process them, some of the effects may include:
payload
The general form of a parameter definition is: @annotation name:<>type[kind]*~=default
. Do remember that parameters also can be used in either declarations or rule matching, the syntax looks the same but there are different options in either case.
Here are the annotations you can use for parameters/arguments:
name
name:String
names:String*
name?
name ~= "Jane"
Types are generally inferred, but when ingesting data, declaring types for messages is a good idea. studentList : Student*
or age:Number
Here are some examples:
$class Student (@key name:String)
$msg calculateGravity (mass:Number, g = 9.8)
The sample values are used when generating the messages automatically, especially in sketch mode
, see Flags and modes.
When matching arguments to a rule, there is a slightly different set of expressions available, example:
$when caught.skipping.class (students:Student*, when:Date)
$when a.factorial (num is 0)
$when diesel.rest (path ~path "/v1/ems/:env/device/:deviceType/:deviceId", verb == "GET")
$when diesel.rest(path ~= "/myActualServer/create/(?<user>.+)")
Messages can be decomposed in a Spec, via matcher rules, with the $when
construct:
$when subjectMatch.verbMatch (parmMatch) IF => IF subject.verb (parms)
When a suitable match is found, the resulting message will be created. IF is an optional condition to apply this decomposition rule. You can see more details about Expressions and pattern matching.
$when home.guest_arrived(name) => lights.on
$when home.guest_arrived(name=="Jane") if (isRaining == "true") => chimes.welcome(name="Jane")
$when lights.* => lights.check
$when *.sendtest => (rule12=true)
$when dieseltestsendtest.* => (rule16=true)
$when dieseltest.send.multiple.* => (rule18=true)
$when /dieseltest.*/ => (rule19=true)
$when /dieseltest\..*/ => (rule19a=true)
There's also a multiline version:
$when home.guest_arrived(name)
=> if (name!="Jane") lights.on
=> if (name=="Jane" && isRaining == "true") => chimes.welcome(name="Jane")
$when
is the message implementation, while $mock
is the message's mock.
Alongside the $when
rules, you can define $mock
rules - these are used when mocking the message - see Mocking services++ and Flags and modes.
A Story starts with a message. This is either simulated when testing or sampled in an external message stream++.
$msg subject.verb (parms)
is used to simulate a message.
$sample subjectMatch.verbMatch (parmMatch)
is used to sample messages.
$msg home.guest_arrived(name="Jane")
When either is seen (simulated or sampled), and it meets the conditions of the first message in the story, the story is "triggered" and the expectations are checked.
Messages can be triggered via the Diesel API as well.
If the message was simulated as a result of running the story in test mode, then the following messages are also triggered and tested.
If the message was observed during normal runtime and we only need to test it, the story will progress as further messages are observed during the same runtime (i.e. their appearance is tested).
When a story is executed, its expectations are being tested:
$expect subjectMatch.verbMatch (parmMatch)
Such as:
$expect (greetings=="Greetings, Jane")
$expect chimes.welcome(name=="Jane")
Note the difference between checking for a message with values versus the version to check just for values.
When running in mockMode
, messages can be mocked instead of actually "executed":
$mock subjectMatch.verbMatch (parmMatch) => (parms)
Examples:
$mock lights.check => (lights="bright")
$mock chimes.welcome => (greetings="Greetings, "+name)
When not running in mock mode, messages will be executed. A suitable executor will be found and passed the message.
Some executors are defined by default, such as ctx
or wiki
, see Default executors.
Otherwise, you can extend the system by creating your own executors, see [The SDK]].
Message execution is naturally asynchronous, in the engine's internal implementation. However, logically, the execution is treated as synchronous. The engine uses Akka actors internally, to execute each message individually, in its own separate execution context.
This represents the ultimate in flexibility but also ads some complexity for the users:
For instance, the message $msg <POST> subDb.createSub
is an asynchronous REST call, consisting of a request and then a reply on a different actor/thread.
Thus, when the message is triggered initially, it is not in fact complete, as logically, it should wait for the response.
The executor of the message is responsible for notifying the engine when the message is completed. At this point, the engine will consider the next messages in a sequence.
See more in Concurrency, asynchronous and distributed.
You need to log in to post a comment!