Skip to content

razie/diesel

master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

DIESEL

License

Reactive rules and workflow DSL

An asynchronous, message-oriented workflow framework, driven by rules and layered on top of akka actors. The rules are based on pattern matching:

$when math.fact (n == 0)
=> (payload=1)

$when math.fact (n > 0)
=> math.fact (n=n-1)
=> (payload=payload * n)

See more details here:

Testing

The testing framework relies on similar DSL constructs, to define tests and then a Guardian, to run these continuously.

$send math.fact (n=0)
$expect (payload == 1)

$send math.fact (n=5)
$expect (payload == 120)

See more details and technical notes at diesel.

Asynchronicity and parallel flows

Each step in a flow is asynchronous, but the flow will chain them, giving the appearane of synchronous execution, by default. Several mechanisms are available for full control, here is one example of controlling execution with a flow pattern:

$when flow.start => flow.step1
$when flow.start => flow.step2
$when flow.start => flow.step3
$when flow.start => flow.step4

$flow flow.start => (flow.step1 + (flow.step2 | flow.step3) + flow.step4)

Quite intuitively, when the rule flow.start is identified and it has the respective steps generated, this $flow pattern will rearrange the steps to occur in the given sequence (intuitively, steps 2 and 3 are executed in parallel, between steps 2 and 4.

Note the separation of the decomposition of rules and the flow pattern. If the $flow was not given, the steps would run in sequence, but somewhat random. The only way to ensure they would run in sequence, would be to write it like this:

$when flow.start 
=> flow.step1
=> flow.step2
=> flow.step3
=> flow.step4

... that or add a $flow rule to sequence them. The idea of separating $flow is that parallelization is usually an optimization activity, which does not or rather should not really impact the logical behaviour of the flow.

Other expressions are available, such as:

$when starting.something
=> sequential.step
==> fire.and.forget     // an async sub-flow
<=> separate.async.flow // spawn an asynchronous sub-flow but wait for it's result

These can be bundled and controlled in other rules.

Streams add another level of parallelism.

REST APIs

There is a simple binding to REST, using the diesel.rest message (you can call this at: https://specs.dieselapps.com/api/mock/myActualServer/create/John ):

$when diesel.rest(path ~= "/myActualServer/create/(?<user>.+)")
=> myMailServer.create (user)

We can mock a few examples of this service:

$mock myMailServer.create (user == "John") 
=> (payload = {
  status:"Success"
  })

Note that instead of $when it uses a $mock and that matches the API call prefix (.../mock/...). Using /mock/ in the API enables the $mock rules and it's very effective in development.

Also, when using diesel.rest a few advantages: you can use named groups in the regex, but you can also use classic .../mypath/:element/:id etc. Also, the query parameters are automatically populated in the context, etc. See more at rest mocks.

Expressions

The expressions used in Diese are useful on their own: as an external DSL, the expressions are fairly complex (see more in expr), including lambdas, list operators and inlined Javascript expressions, such as:

// array/lists with lambdas etc
[1,2] + [3] filter (x=> x > 1) map (x=> x + 1)

// embedded JS, when you run out of constructs
js:{var d = new Date(); d.setSeconds(d.getSeconds() + 10); d.toISOString();}

See expr for details and info on using them on their own.

Execution traces

The "execution trace", a tree-like model is stored and can be retrieved to see what is going on, or what happened for a past request. This will represent, at all times, the current state of execution (some nodes may be async operations that we're waiting for etc).

alt diesel tree

Diesel Apps

Around the main Diesel DSL for reactive rules, we created an entire Scala DSL framework for developing domain and rules-driven reactive services and apps. Rapid mocking, prototyping, development, testing and hosting of (micro)services and websites, see The simplest micro-service you ever created

See it in action and go serverless at [DieselApps.com](http://www.dieselapps.com).

You can either use the DieselApps cloud, embed the rules or the entire framework in your app or run your own instances on-prem,

Components:

  1. diesel - the light reactive rules-based workflow engine
    • expr - expressions and parsing
    • tconf - TBD, for specs-driven logic
    • dom - TBD, domain entities
    • db - simple entity persistence layer for Mongo
    • diesel-snakk - snakked on steroids: simple REST snakking, XML and JSON template parsing etc
    • diesel-rest - mocking of REST services
  2. diesel-wiki - A domain-driven Markdown Wiki - the basis for configuration, text-first, with support for extensible DSLs
  3. diesel-play - the play code to make everything work as a website

Head over to the academy to read more!

Diesel-wiki

Domain driven markdown wiki. See more at Markup_and_DSL.

Versions and technologies

  • scala 2.11
  • akka 2.4, with akka-cluster etc
  • play framework 2.4

Examples

Mock a simple REST API - see The simplest microservice you ever created:

$mock say.hi (name ?= "Jane") => (greeting = "Hello, " + name)

Test the simple REST API - see Simple microservices testing:

$send say.hi (name = "Jane")
$expect (greeting contains "Jane")

OR via the implicit REST API:

snakk.text (url = "http://specs.dieselapps.com/diesel/mock/say.hi?name=Jane")
$expect (payload contains "Jane")

The implicit REST API is implemented in Play Framework controllers, which you can include in your routes, see Routes.

Scala client - use as a rules library

You can use this library directly in your code, in several ways, see some samples or unit tests.

implicit val system = ActorSystem("testsystem", ConfigFactory.parseString(""" """))

// tell the engine to use this system
// tell the engine to use this system
DieselAppContext
    .withSimpleMode()
    .withActorSystem(system)

// make a DSL spec - the rules we will run
val spec = DieselTextSpec (
    "spec_name",
    """
      |$when home.guest_arrived => lights.on
      |
      |$when home.guest_arrived(name=="Jane") => chimes.welcome(name)
      |
      |$when chimes.welcome => (greeting = "Greetings, "+name)
      |""".stripMargin
  )

  // make a story DSL - the starting sequence of events
  val story = DieselTextSpec (
    "story_name",
    """
      |$send home.guest_arrived(name="Jane")
      |""".stripMargin
  )

// run it: create engine, run story and wait for result
val engine = DomEngineUtils.execAndWait(
  DomEngineUtils.mkEngine(
    new DomEngineSettings().copy(realm=Some("rk")),
    List(spec),
    List(story)
    )
  )

println(engine.root.toString)    // debug trace of engine's execution
println(engine.resultingValue)   // resulting value, if any

// test it
assert(engine.resultingValue contains "Greetings, Jane")