Skip to content

Tutorial

The Collaborative State Machines model is created around the concept of a collaborative state machine, the root of a hierarchical structure consisting of state machines, their nested state machines, and states. At each level of this hierarchy, data can be declared that is accessible locally as transient data, as well as data declared at the root which acts as persistent data. This architecture represents a hierarchy designed specifically to describe distributed systems that span many distributed resources, allowing a developer to focus on the entire system instead of fragmented components. Each state machine is an autonomous, reactive entity that interacts with external services and other machines through events defined in a high-level declarative language called CSML using Pkl syntax.

Collaborative state machine

To begin implementing an application, you first declare the collaborativeStateMachine root. This block serves as the container for the entire system logic. In this specific implementation, we define a global message variable within the persistent block. This variable acts as a shared memory space that different state machine instances will populate during their execution lifecycle.

collaborativeStateMachine {
  persistent { ["message"] = "''" }
  stateMachines {
    // State machine definitions will follow here
  }
}

State machine

The next step involves defining the individual state machine logic within the root. The first machine, named one, is designed to initiate the workflow. Upon entry into its initial state a, it appends a string to the global message variable and emits an event with the topic advance specifically targeting the second state machine. It then waits for a returning advance event to transition to state b, where it logs the final accumulated message and terminates.

    ["one"] {
      states {
        ["a"] = new Initial {
          entry {
            new Eval { expression="message += 'Hello, '" }
            new Emit { event { topic="advance" }; target="'two'" }
          }
          on {
            ["advance"] { to = "b" }
          }
        }
        ["b"] = new Terminal {
          entry {
            new Log { message="message" }
          }
        }
      }
    }

Following the first machine, you define the reactive behavior of any subsequent machines. Machine two remains in its initial state a until it receives the advance event from the first machine. Once triggered, it moves to state b, appends its own contribution to the global message, and emits an event back to machine one to signal that its phase of the collaborative process is complete.

    ["two"] {
      states {
        ["a"] = new Initial {
          on {
            ["advance"] { to = "b" }
          }
        }
        ["b"] = new Terminal {
          entry {
            new Eval { expression="message += 'World!'" }
            new Emit { event { topic="advance" }; target="'one'" }
          }
        }
      }
    }

Instantiation

The final step in the implementation is the instantiation of the defined state machine classes. By adding an instances block, you tell the CSM runtime to create active instances of the logic blocks defined previously. This bridges the gap between the declarative class definition and the actual execution of the distributed system.

instances {
  ["one"] { stateMachineName = "one" }
  ["two"] { stateMachineName = "two" }
}

More examples

Discover more in the Examples gallery.