DSL Reference Pub

The main di:es:el language reference, explained through examples rather than grammars etc.

A message or fact or call can trigger rules.

Stories and specs support different constructs: stories can create and receive messages, while specs can only define messages and rules. Stories are essentially tests and use cases, while Specs are used for rule, logic and mocking.

A rule (defined with $when) or a mock for a rule (defined with $mock) can decompose a message into other messages, send requests and process responses from other systems or databases, set values in the context etc. Why we chose rules as opposed to classic structured programming is flexibility and minimal coding. You'll see that pattern matching is a very powerful tool, compared to classic if/else/switch constructs.

The number of language concepts is small:

  • Specs: $msg, $when and $mock
  • Stories: $send and $expect
  • Other constructs: $val, $def
  • Domain modelling (separate component): $class, $object

Stories

Stories are used to send a message and then test some expectations about it:

$send snakk.text(url="https://www.google.ca")
$expect (payload contains "html")

The DSL above will look like this, when rendered in a page (if it doesn't look like this, then it is not valid syntax):

send::  msg snakk.text  (url ="https://www.google.c"...="https://www.google.ca")

expect::  (payload contains "html")

In a story, this is the test trigger. When "running the story" this will send the message.

The expressions available in parameter/argument lists are fairly intuitive, see Expressions and pattern matching or the full set of tests we use for the engine itself: expr story.

Expectations

Tests are expected to follow a $send, here's another example.

$send subDb.create(name="Jane", address="12 Greenbriar, Aurora")

$expect (subId ~= "sub[0-9]*")
$expect (name=="Jane")

There is a variety of expressions available for $expect, see Expressions and pattern matching or the full set of tests we use for the engine itself: expr story.

We can also expect messages, not just values.

$expect entity.method (parms)

Specs

Specifications are used to mock services or implement actual rules.

Mocking

Mocking will simulate a message and populate some values as if they were returned by the real execution of the message. This would be a typical lookup in a database or another service; a logical service to calculate something etc.

$mock chimes.welcome => (greetings="Greetings, "+name)

0 $mock:: chimes.welcome
   . (greetings=("Greetings, " + name))

or just make up a value, unrelated to a message (settings whatnot):

$val name:String="gigi"

val name="gigi"="gigi"

The => means decomposition, i.e. an initial message is decomposed into more messages. When decomposing into more than one message, put each message on its own line, startig with the => symbol:

$mock cust.addToCart
=> inventory.check
=> $if (result=="ok") cart.addItem
=> $if (result!="ok") (error="Not enough items")
=> $else (message="this branch will weirdly trigger when adding items - read the preceeding IFs again...")

When

$when constructs a rule, i.e. when a message is matched, generate another message. This is how most systems work: they receive messages and send messages in response (decompose messages).

$when home.guest_arrived(name=="Jane") => chimes.welcome(name="Jane")

$when:: home.guest_arrived (name == "Jane")
   chimes.welcome (name="Jane"="Jane")

This could also be written with an $if - this is suitable if a condition uses context variables not just message input:

$when home.guest_arrived $if (name=="Jane") => chimes.welcome(name="Jane")

A system can use both mocks and rules of the same messages. If a rule is found, it will run instead of the mock, unless the system is running in mock mode, see Flags and modes.

if and else

Each decomposition inside the IF will accept a "guard" condition, as either an $if or an $else:

$mock cust.addToCart
=> inventory.check
=> $if (result=="ok") cart.addItem
=> $if (result!="ok") (error="Not enough items")
=> $else (message="this branch will weirdly trigger when adding items - read the preceeding IFs again...")

The $else applies to the last preceeding IF - in the example above the $else will be activated when the result is ok...

Archetypes and flags

You can add various flags to the rules, liek so:

$when <fallback> home.guest_arrived $if (name=="Jane") => chimes.welcome(name="Jane")

The supported flags now are:

  • fallback - see Falback rule below
  • exclusive - see Exclusive rule below
  • trace - the result of this rule will be shown only in trace mode
  • debug - the result of this rule will be shown only in debug mode

Fallback rule

A fallback rule will be applied if no other rule with the same entity.method combination is applicable to the message. This is useful to have a "default" version of a rule apply.

For isntance, if you call the rule below with 3 the fallback rule will not apply, since there already was a rule for that message applied already.

$when testdiesel.fback1(r156==3)
=> (r156=r156+2)

$when <fallback> testdiesel.fback1(r156)
=> (r156=r156+1)

Exclusive rule

If an exclusive rule is applicable to the current message / event, it will apply exclusively and no other rule of the same entity.method will apply.

asAttrs

This is a special transformation that can be applied to a json document, to flatten it into a list of parameters that can be used to call a message:

$when something.hapenned ()
=> (j = {a:1, b:2})
=> something.else (j.asAttrs)

j would be flattened and a and b would be parameters to the new message something.else.

Indentation

You can use indentation to simulate regular blocks:

$when testdiesel.else1(branch)
=> $if(branch) do.this
|=> testdiesel.else.if
|=> (res131="if")
=> $else do.this
|=> testdiesel.else.else
|=> (res131="else")

Note that do.this does nothing, it's not a recognized message. You can use diesel.branch to avoid an "unknown" tag.

Instead of spaces, you use | to ident - as many as levels deep you want to go. Note the use of do.this this is a message that does nothing, but it is needed to become the parent of the indented nodes underneath. You can use any message you want - but to avoid confusion, use a message that has no rules handling it.

Although not very rule-friendly, this may help avoid many rules that apply in only one case.

Sequences

This is a sequence, with guards based on the results of the previous activities:

$when cust.addToCart
=> inventory.check
=> $if (result=="ok") cart.addItem
=> $if (result!="ok") (error="Not enough items")

$when cust.addToCart => (haha="nok")

$mock inventory.* => (result="ok")

The second when rule here is not part of the sequence and would run in parallel with the sequence.

Functions

There are a number of built-in functions, like sizeOf and now - see Expressions and pattern matching for details.

You can also embed Javascript logic as "functions":

$def my.func(p1,p2) {{
return p1+p2;
}}

You can call this by just raising a message with the same name:

$send my.func (p1,p2)

or by using a when to generate one:

$msg sample.msg2 (p1="a", p2="b")
$when sample.msg2 (p1,p2) => my.func (p1,p2)

This allows you do quickly embed logic, without super programming:

  • simple table lookups (for either configuration or to replace simple external services)
  • simple calculations (average a list of numbers or such)
  • transformations

Writing JS

You can access only Java classes in a package with the "api." prefix.

You may or may not use the return statement - for simple expressions it is not needed.

The diesel object

You can use the predefined diesel object:

  • diesel.get("attr")
  • diesel.set("attr")
  • diesel.msg(msgString)
  • diesel.engineId()

Predefined objects

ctx

  • ctx.persisted
  • ctx.clear

func

  • func.f - call a function defined with def

Snakking

You can easily interact with REST services via JSON, XML or simple text, see Snakking REST.

Object types

Currently all parms are strings.

JSON will be supported (map, array, string, number) and structure.

Execution: sync vs async vs parallel

This is very important - if you're not used to synchronous or parallel programming, you may be caught off-guard. In this case, turn everything synchronous with $ctx.sync.

While storytelling is naturally synchronous, the execution is by default asynchronous, i.e. somewhat out of order.

So, for instance, this:

$when a.a => sub.load (name="John")
$when a.a => ctx.log (subId)

$mock sub.load => (subId="1234")

Will be resolved in the following sequence:

  • sub.load and ctx.log are expanded in the tree
  • they are both started at the same time

If you're looking for a sequence, use this version - it will do what you expected:

$when a.a
=> sub.load (name="John")
=> ctx.log (subId)

In this case, the executions are still asynchronous, but a.2 is started only when a.1 will finish, so in essence they run in sequence .

Even further, you can do stuff like:

$when a.a
=> sub.load (name="John")
=> $if (error != "") ctx.log (subId)
=> $if (error == "") ctx.log (error)

AHA, so it's parallel or concurrent execution! Not really! It is asynchronous, i.e. out of order.

Activities are actually executed in the same actor/thread, so they are ran one after another. Therefor, you have no concurrency issues. However, if their implementations are asynchronous (like snakking or I/O) then those run in parallel with all others.

...it can get tricky. You should remember that messages are assumed to be asynchronous microservices. So, sub.load would take some time and a network call. If you do want to wait for it, you have the option... but if you don't, then you don't have to.

Simulate a callback

So, what if we wanted to simulate a callback?

$when a.a
=> sub.load (name="John")
=> call.back (subId, error)

This will launch sub.load asynchronously and, when this particular call is completed, the call.back is activated.

Guards, conditions and pattern matching

The $mock and $when are rules which are triggered only when matching the current message. Here is one example that matches any cust.addToCart message:

$when cust.addToCart
=> $if (type == "residential") bill.now
=> $if (type == "commercial") bill.later

And

$when cust.addToCart (type == "residential") => bill.now

$when cust.addToCart (type == "commercial") => bill.later

Expressions

Wherever a value is expected, you can enter a variety of expressions, see Expressions and pattern matching.

Story telling

This is very important when your story involves a sequence of events. versus a random sequence of events.

Stories are interpreted top to bottom. All $msg are generated and each subsequent $expect is ran AFTER the message finished.

Each message however is run asynchornously, so all messages by default are started in sequence.

You can control this with $ctx.storySync which is the default - this will set the story telling mode to synchronous, so all messages are ran in sequence.

The opposite is $ctx.storyAsync which will start all messages asynchronously. Note that unintuitively, after setting the story mode to async you can switch it back to sync, as the story itself is interpreted in sequence.

Watcher mode

TBD - there is a watcher mode, when the stories are continuously checked against a live stream of messages.

$send subDb.create(name="Jane", address="12 Greenbriar, Aurora")

$expect (subId ~= "sub[0-9]*")
$expect (name=="Jane")

The two tests above will be applied whenever a message subDb.create matching those parameters will be seen. This way, integration or unit tests can be tested all the time, even in production.


Was this useful?    

By: Razie | 2016-08-23 .. 2021-02-04 | Tags: academy , reference , dsl


Viewed 202 times ( | Print ) this page.

You need to log in to post a comment!

© Copyright DieselApps, 2012-2021, all rights reserved.