Sushi, a Kotlin based Flow Design Tool

Philosophical Background

Intelligence is the ability of imagination or simulation of environment given it’s constraints and laws. This simulation can be defined in terms of algorithms and blocks of rational expressions. Programmers learn the way how to make these simulations in a language that computer can understand, but then this becomes in a way that is not intuitional enough for others to understand.

It’s so important to be able to divide your flow of thoughts into smaller pieces of work. This way you can better track and debug the flow and be able to handle more complex situations.

Motivation

It becomes so complicated to keep track of the flow of work you write programmatically after some time. Especially when you apply unwanted customizations in small units of work in your flow. State Machines could be a good concept for solving these kind of problems. There are various implementations for state machines out there in different languages.

What is Sushi?

Sushi is made of blocks. You define blocks with their types and connect them. There is three different type of blocks.

  1. Action
  2. Branch
  3. Container

1. Actions

These kind of blocks are the main ingredients to build a delicious sushi. Each action is based on it’s type which shows what it actually does. By looking at the figure below you can understand how an action actually works.

2. Branches

If you want to control your flow based on conditions you can use Branch blocks.

Sushi comes in two different artifacts, core and service. If you would like to use the core library of Sushi in your project where you define your own actions using Sushi’s API, you need to go with sushi-core version. In case you would like Sushi take care all things for you as a self-contained server managing persistence and running the flows for you, then sushi-service is your choice.

A DSL for building flows

The beauty of Sushi becomes clear when you actually start using it. There are three ways to build flows in Sushi.

  1. Programmatically, using Kotlin DSL. (You need to know Kotlin)
  2. Using TOML configuration files. (You need to know how to configure blocks)
  3. Using UI to generate the flow. (You just drag and drop visually)

1. Define Flows Programmatically

You only need to create a list of blocks and specify what would be the next blocks. Then Sushi’s engine will take care of wiring and validating your flow.

val flows = mutableListOf(
    Action().apply {
        name = "input 1"
        id = "1"
        type = "constant"
        source = true
        params = mutableMapOf("value" to "5")
        nextBlocks = mutableListOf("delay-1")
    }, Action().apply {
        name = "input 2"
        id = "2"
        source = true
        type = "constant"
        params = mutableMapOf("value" to "3")
        nextBlocks = mutableListOf("delay-2")
    }, Action().apply {
        name = "Delay 1"
        id = "delay-1"
        params = mutableMapOf("seconds" to "2")
        type = "delay"
        nextBlocks = mutableListOf("3")
    }, Action().apply {
        name = "Delay 2"
        params = mutableMapOf("seconds" to "2")
        id = "delay-2"
        type = "delay"
        nextBlocks = mutableListOf("3")
    }, Action().apply {
        name = "Log the result"
        id = "3"
        type = "log"
    }
)

flowEngine.wire(flows)
flowEngine.executeFlow()
flowEngine.await()

You can even inject objects using params of actions to do complicated tasks with dependencies. Engine will take care of wiring and blocks dependencies.

2. Using TOMLs

  • Isolated blocks
  • You define what actions do by their type.
  • next shows where to go after this state, can be multiple blocks.
  • Define your parameters as more as you want.
[[action]]
    name = "intial action"
    source = true
    type = "log"
    id = "initial-action"
    next = ["new-defined-action"]

[[action]]
    name = "new defined action"
    type = "my-container"
    id = "new-defined-action"
    next = ["4"]


[[branch]]
    name = "branch-1"
    id = "4"
    on = "value"
    [branch.mapping]
        branch-1  = "5"
        branch-2 = "6"
        branch-3 = "7"

3. Using UI

It’s so easy to add actions to your flow. You can search in the library of actions which consists of custom-made public actions by other users. Any change on the graphical flow will change the TOML configuration synchronously.

Editing actions is also something you can do with UI. Changing the parameters and editing source blocks is also configurable from UI.

Sushi UI helps:

  • Just add blocks visually and connect them to each other.
  • Sushi Engine will take care of validations of the flow.
  • You can execute the flows from UI and get the results.

UI is connected to the backend with websocket that gets updates from backend on the fly while executing the actions. This is very useful to get the updates and debug your flow on while developing it.

ui

Power of Sushi

Power of sushi comes with the Containers. It allows us to define complex combination of blocks with specific parameters and re-use them easily. Anyone can define custom actions and make it public or private. By defining more useful public containers you can help others to reuse your containers too.

This makes the real difference here with other libraries/tools. Being able to abstract the complexity in different layers is the desirable functionality while defining a complicated flow.

Work To be Done

Sushi is still a work in progress, but there is a lot of potential in it already which can be used to build microbots.

Sushi is based on defining blocks with next block id, but there is also another way of defining the flows by specifying goals and dependencies for them. This way to build your flow in a different way from end to start. Each Block needs some dependencies to be fulfilled and this happens recursively until a goal is reached. This is called Slot Filling.

In the future we will use more like a hybrid way of defining flows by introducing this feature which opens the door for more interesting world of flows.

comments powered by Disqus