Skip to content

1. Creating a Schematic

Ovan Crone edited this page Apr 11, 2018 · 17 revisions

What is a Schematic?

A schematic is the definition of a process (state-flow). You will define a schematic once for a certain purpose, such as how to send an email, make an API call, or more complicated flows. That schematic can then be used as the source code for instances of that flow called REstateMachines, or simply "Machines".

How to make a Schematic

REstate provides a SchematicBuilder that helps guide you through creating a schematic from scratch. Let's first take a look at what we will be building, then we will go line by line, breaking it down.

The final code will be:

var schematic = REstateHost.Agent
    .CreateSchematic<string, string>("LoggerMachine")
    .WithState("Created", state => state
        .AsInitialState())
    .WithState("Ready", state => state
        .WithTransitionFrom("Created", "log")
        .WithReentrance("log")
        .WithAction("log info", onEntry => onEntry
            .DescribedAs("Logs the transition as a message.")
            .WithSetting(
                key: "messageFormat", 
                value: "{schematicName}({machineId}) entered {state} on {input}. " +
                       "Message: {payload}")))
    .Build();

Creating a Schematic

To begin we call the create schematic method.

var schematic = REstateHost.Agent
    .CreateSchematic<string, string>("LoggerMachine")

The type parameters on the method correlate to state and input types to be used in the Schematic, as seen in the definition: CreateSchematic<TState, TInput>. The only parameter is the name of the Schematic.

Defining an initial state

Next we need to define our initial State, the State the Machine will be in when it is created.

    .WithState("Created", state => state
        .AsInitialState())

The State value is the string "Created". State values should be unique.

The second parameter is a lambda that allows you to modify the definition of the state generated. Here we use the .AsInitialState() method.

Next we can define another State, but this time with an Action: a transition from the "Created" state, and a special type of transition called a re-entrance, which is to say there is a transition from the state into itself, like a loop or recursion.

Defining a State

First, we define the State "Ready" and start the State modifier lambda like before.

    .WithState("Ready", state => state

Transitions

Then, we can define a transition from our first State '"Created"' into our new State "Ready" when the Machine receives the input "log".

        .WithTransitionFrom("Created", "log")

Re-Entrant transitions

Then, we can allow the re-entrant transition, "Ready" -> "Ready" when the Machine receives the input "log".

        .WithReentrance("log")

Now that we have the acceptable transitions defined, we can add an Action that should execute when the machine transitions into the state.

The first parameter is known as the ConnectorKey. It is just a string that corresponds to a Connector (and its associated configuration) that has been registered with REstate. REstate ships with a LogEntryConnector that has several configurations available: "log info", "log debug", "log trace", "log warn", "log error", "log critical", where the second word is the logging level to use. For this example we use the "log info" connector key since our log message provides some "business value".

The second parameter is a lambda that lets us further define the Action, just like we did previously on State.

        .WithAction("log info", onEntry => onEntry

Action

An Action can be given a description to make it less cryptic.

            .DescribedAs("Logs the payload as a message.")

Action Settings

Going further, we can add some settings to our Connector. The settings the Connector will use vary per Connector, so refer to their documentation for what is available and how to use them, but we can take a look at the log Connector now.

The log Connector has just one possible setting: messageFormat. When provided it lets you specify the format of the message that will be logged. There are a number of allowed special tokens such as {schematicName} that will be replaced at run-time with the current Machine's values.

            .WithSetting(
                key: "messageFormat", 
                value: "{schematicName}({machineId}) entered {state} on {input}.")))

Building the Schematic

Finally to wrap it up, we can call the .Build() method to convert the SchematicBuilder<TState, TInput> to the serializer-friendly Schematic<TState, TInput>.

    .Build();

What's next?

We will continue with the Schematic we just built and Create a StateEngine to store it.