The variable scoping rules are important. The scopes cover not just variables, but also error handling, parallel processing etc.
Values can be declared outside of a message with
$val, in either specification or a story. If declared inside a specification, it will exist in all flows using that specification. Note that these objects are not immutable, they can be changed, so they really are variables.
Inside a rule, variables and assignments look like a message without a name. Note that
someValue is local and will not be seen outside this rule's scope, see scopes below:
$mock rule.a => (someValue=123)
Changes to complex objects look the same - this assumes an object
student already exists in context:
=> (student.name = "Joe")
variable or when itself contains dots and other special characters:
=> (student["some.weird.attribute.name"] = 12) => (student[variable] = 12)
Some special prefixes manage the scope of variables (see scopes below):
ctx- will place the variable in the closest enclosing scope (
ctxis deprecated, use
dieselRoot- place the variable in the root context for this flow
dieselRealm- place the variable in the static context shared by all flows
Any flow has a
root and many levels of
scopes. A scope may be created automatically by certain constructs or explicitely with the
Also, each rule has is its own little scope for local variables, referenced as the rule scope:
$when sample.rule1 => (v1 = 1) // not available in rule 2, but is available in other rule1's => (dieselScope.v2 = 2) // is available in rule 2 $when sample.rule2 => (v1 = v1 + 1) // v1 is Undefined => (v2 = v2 + 1) // v2 should be 3 $when sample.rule1 // another overloaded rule1 => (v1 = v1 + 1) // v1 is Undefined $send sample.rule1 $send sample.rule2 $expect (v1 is 2, v2 is 3) Note that you can also access the values in scopes with the accessor notation - especially useful when the name is available in a variable: => (a = dieselScope["v2"]) => (variable = "v2") => (a = dieselScope[variable])
The scope hierarchy follows these rules:
studentin root and then re-assigning it inside another rule, the second will overwrite the top).
The realm is a static context shared by all flows running in your project. This is a good place for such things like tokens etc. Note that this context is small and slow.
The root is the root context for each "engine" or "flow" and contains globals.
You can set global values with a special scope:
dieselRoot.x = expr1.
Note: be careful with using this scope: if you code lists, loops, asynchronous streams or parallel execution, explicitely putting values in this shared context can create a lot of hassles, just like static variables do in structure programming languages. This is why we use the scope context instead.
A scope contains all variables for that context. You can assign values in scope with
dieselScope.x = expr2 so
x then becomes a new variable in the closest enclosing scope. You should not set variables outside the closest enclosing scope, as this would defeat the purpose of automatic scopes for instance in parallel processing.
When you read a value
x, the scopes are searched from the current scope and up and the first occurence of
x is returned, if found.
Remember that scoped values do not cross outside the scope, by default.
Scopes are created automatically following these rules:
In all normal flows, you should use only local variables and at most scope variables. You should try to avoid the
realm contexts as much as possible.
These are the ways to propagate values into the scope context:
$when test.rule1 => (v1 = 1). // v1 is local only to other test.rule1 derivations, not seen outside these $when test.rule2 => (dieselScope.v2 = 2) // dieselScope prefix will put v2 in the scope, so it's shared within the scope $msg test.queryStudent (id:String) : (student:Student, alternatives:Student*) // declaring value outputs will propagate them to scope $when test.queryStudent(id="1") => (student = x) => (alternatives = ) The student and alternatives above are not local - since they were declared as the output of that rule, they are propagated automaticaly to the closest enclosing scope. $when test.rule3 => (payload = 3) // payload is always propagated to the closest scope
Each rule also has it's own scope for local variables. The only things that escape the rule scope are
payload, explicitely declared return values from messages and when using the special prefixes like `dieselScope'.
When setting values, the scopes are as follows.
=> (a=b) will set the value in the context of the closest parent
=> (dieselScope.a = b) will set the value in the closes enclosing scope (above the parent, likely)
.todo this is not available yet:
=> realmVars.a = b) will set the value at the realm level
By default, the variables are local to the rule inside which they are created. For instance the variable
student below is not visible to the second rule:
$mock test.getStudent => (student = 'a student') $mock test.printStudent => ctx.echo(student) $mock test.students => test.getStudent => test.printStudent
To have this simple example work, you should use
dieselScope.student = 'a student' OR use
payload instead of student.
The only variables that can cross outside a rule are:
Otherwise, you can use the
This example would work, because
student is declared as the output of the rule, so it is propagated to the closest scope, outside the parent rule:
$msg test.getStudent : (student) $mock test.getStudent => (student = 'a student') $mock test.printStudent => ctx.echo(student) $mock test.students => test.getStudent => test.printStudent
Be careful when declaring and naming output variables.
Very bad idea: $msg my.smart.message() : (output, result) This message will put generic names in the static scope and these can easily overwrite others later and cause havoc. Same can be done by this: => (dieselScope.result = "something")
The idea of output domain values is to simply using domain elements in a scope. For instance, if your flow works on an
Account, it is easy to declare the
accountId as outputs of some query messages and then they'll be available anywhere in scope, wihtout having to declare the in each and every message like weak structured langauges (Java).
If there is a chance that a domain variable can cause clashes within the same flow, don't put it in scope or declare it as output of messages.
So here are some concrete examples:
BAD: $msg warehouse.check (productId, quantity) => (result) $msg warehouse.reserve (productId, quantity, cartId) => (reservationId) $msg warehouse.provision (reservationId) => (result) DECENT: $msg warehouse.check (productId, quantity) => (warehouseCeckResult) $msg warehouse.reserve (productId, quantity, cartId) => (reservationId) $msg warehouse.provision (reservationId) => (provisioningStatus)
Or, better yet, if the check and provision have only local application, don't even give names to their output, use
payload which is expected to be overloaded.
Here are the design notes on contexts, for contributors or if you have some SM tendencies...
This is a virtual context, backstop of all engine contexts. Contains statics shared across all engines in that realm. It is slow.
It is the root context for each engine, contains statics, pre-defined variables etc.
A scope context. It stops parameter propagation up.
A local context, does not propagate values up, similar to the ScopeCtx but different use.
This is a static context - it does not allow updates, used for evaluating expressions where setting values are not permitted.
Same as the local, but special use for rule scope (each
$when has its own small local variable scope, so they behave intuitively)