Stop

Stop is a language for defining software systems.

The goal of Stop is to serve as a software blueprint. It is a tool that allows developers to focus and communicate the goals of a system without specific programming language implementation. Stop defines the data model, states and transitions of a software system.

Editor

The official Stop web based editor features syntax highlighting and real time reference, transition and logic checking. Try writing sample Stop systems as you read the documentation below.

Roadmap

The language is just the start. The plan is to map a Stop definition directly to a running software system where state implementation can be written in a variety of programming languages.

Why is it called Stop?

Because Go is popular and it's not Go. It's Stop. Also, a key concept of the language is finding a stopping state.

Hello World!

Here is a Hello World! of a simple Stop file.

    start Hello {
        string whom
        -> Say
    }
    
    stop Say {
        string words
    }
        

This system satisfies the minimum requirement of at least one start and stopping state. There is one transition from the starting Hello state to the stopping state of Say. The Hello state would require an implementation that would transition only to the Say state with the correct arguments (in this case a single property of type string called words).

States

Stop revolves around one main type called a state. Everything in Stop is a kind of state.

The simplest version of a state looks like a basic data object. Every property is required and there is no concept of null. If a property should be empty it should contain the default value for the type.

    User {
        uint64 id
        string username
        string email
    }
        

A state can define transitions to other states.

    Router {
        Request request
        -> Login
        -> Authenticate
    }
        

A state can throw an error state and transition to it.

    UserNotFoundError {
        string message
    }

    Authenticate throws UserNotFoundError {
        string login
        string password
        -> Redirect
    }
        

A state can be asynchronous. Asynchronous states require a timeout to be defined. Timeouts require a transition to be defined in the event the call to this state times out.

    CreatePostTimeoutError {
        CreatePost timedOutState
    }

    async CreatePost {
        Post post
        timeout 30 -> CreatePostTimeoutError
        -> Redirect
    }
        

A state can also yield a single value and return it. Value states are utilized as dynamic properties.

    GetUserTimeoutError {
        GetUser timedOutState
    }

    async GetUser <- User {
        uint64 user_id
        timeout 5 -> GetUserTimeoutError
    }

    Profile {
        uint64 user_id
        User user <- GetUser
        -> Render
    }
        

A state can be a queue state. Queue states are a special kind of state that cannot be transitioned to. Instead they can only be added to with the promise that the queue will state will be executed at some later time.

    CreatePostTimeoutError {
        CreatePost timedOutState
    }

    async CreatePost {
        Post post
        >> PushNotification
        timeout 30 -> CreatePostTimeoutError
        -> Redirect
    }

    queue PushNotification {
        Post post
    }

    stop SendPushNotification {
        string recipient
        string message
    }
        

Properties

States can define scalar, reference, enumeration, collection and dynamic properties.

Scalar properties

Here is a list of the scalar property types that can be used:

Reference properties

A state can define a property with the type of another State. Here a User object has an address property of type Address.

    Address {
        string street
        string city
        string state
        string zip
    }

    User {
        uint64 id
        string username
        string email
        Address address
    }
        

Enumeration properties

Enumerations can be defined within a state. Here an enumeration role is defined and referenced as a property.

    User {
        enum Role {
            STANDARD
            MANAGER
            ADMIN
        }
        uint64 id
        string username
        string email
        Role role
    }
        

Collection properties

A property can also be defined as a collection of either scalar, enum or state types. Here the emails and addresses properties are collections.

    Address {
        string street
        string city
        string state
        string zip
    }

    User {
        uint64 id
        string username
        [string] emails
        [Address] addresses
    }
        

Dynamic properties

Dynamic properties are not required when transitioning to a state with them defined. These properties will be calculated at runtime.

They are defined like any other property but with a <- operator following by a reference to a value state that returns the same property type.

Here in the Profile state user_id would be a required property while the user property is dynamic. The user property will be retrieved by calling the asynchronous value state GetUser with the mapped user_id property. GetUser is responsible for returning a User object within a 5 second timeout. The Profile state is valid after it has gathered all of its required and dynamic properties successfully.

    GetUserTimeoutError {
        GetUser timedOutState
    }
    
    async GetUser <- User throws UserNotFoundError {
        uint64 user_id
        timeout 5 -> GetUserTimeoutError
    }

    Profile {
        uint64 user_id
        User user <- GetUser
        -> Render
    }
        

Optional properties

A property can be made optional by prefixing the definition with the keyword optional. Otherwise properties default to being required.

Optional properties can be left undefined when building an instance of a State. Any Stop runtime implementation should be dynamically checking for optionality during state transition validation.

Use optionals sparingly. State implementations that rely on States with optionals will have to handle the ambiguity presented by properties that could be undefined.

In the example below a body property is optional and likely only used on POST requests.

    Request {
        enum Method {
            GET
            POST
            PUT
            DELETE
            OPTIONS
        }
        Method method
        string path
        optional string body
        [Header] headers
        [Parameter] parameters
    }
        

Transitions

Transitions are defined within a state with the -> operator following by a reference to defined state.

    Router {
        Request request
        -> Login
        -> Register
    }
        

A state with transitions implies a programming implementation. Another piece of code somewhere will decide how this transition takes place but the only possible transitions are to Login and Register. When the state decides to transition that programming implementation will be required to create valid Login and Register states with their required properties.

Starting States

A Stop system needs at least one start state. There can be multiple start states that identify entry points into the software system.

    start Router {
        Request request
        -> Login
        -> Register
    }
        

Stopping states

Every sequence of transitions must eventually end in a stop state. A Stop file is considered incorrect if any transition does not lead to a stop state.

    start Router {
        Request request
        -> Login
    }

    Login {
        -> Response
    }

    stop Response {
        uint32 status
        string content_type
        string body
    }
        

Asynchronous States

By default all states in Stop are treated as synchronous states. In order to designate a state is asynchronous the async keyword needs to proceed the state name.

A state cannot be asynchronous and considered a starting or stopping state. This means a system cannot start ot stop on an asynchronous state.

Asynchronous states require a timeout to be defined in seconds that is greater than 0. A transition must also be defined to handle the timeout occurring. On a timeout the system will halt and transition to the defined state.

The state the timeout transitions to must contain one state property called timedOutState that has references the state from which the timeout transition was defined. Timeouts are managed by an implementing Stop runtime and while a given state may timeout the goal is to transition to a timeout state with some context so there is a chance to respond to a timeout event.

An asynchronous state example:

    CreatePostTimeoutError {
        CreatePost timedOutState
    }

    async CreatePost {
        Post post
        timeout 30 -> CreatePostTimeoutError
        -> Redirect
    }
    

An asynchronous value state that can be used as a dynamic property:

    GetUserTimeoutError {
        GetUser timedOutState
    }

    async GetUser <- User {
        uint64 user_id
        timeout 5 -> GetUserTimeoutError
    }
        

Queue States

Queue states are a special kind of state that cannot be transitioned to. Queues can only be added to with the guarentee that the enqueued item will be executed at some later time.

Queues are a way to handle background or long running tasks like processing an audio file or sending push notifications.

To enqueue an item on a queue you simple create an instance of the queue State. The instance of the queue State you create will then be executed as its own independent state machine at some later point in time when a worker can execute it.

A queue state example:

    async CreatePost {
        Post post
        >> PushNotification
        timeout 30 -> CreatePostTimeoutError
        -> Redirect
    }

    CreatePostTimeoutError {
        CreatePost timedOutState
    }

    queue PushNotification {
        Post post
    }

    stop SendPushNotification {
        string recipient
        string message
    }
    

Notice that the CreatePost state signals that it may enqueue items onto the PushNotification queue with the >> operator. The enqueue action is similar to but not quite a transition. While it eventually invokes another state it does so outside of the current thread of execution at a later time.

An implementing runtime should execute an enqueued state as if it is a starting state in search of a stopping state like any Stop state machine.

Errors

States can throw error states to transition to when something goes wrong. They work exactly like transitions just with a comma separated list of state references after throws. What happens after transitioning to an error state is up to you (just be sure it eventually finds a stopping state).

    RouteNotFoundError {
        string message
        -> Render
    }

    UnauthorizedError {
        string message
        -> Render
    }

    Router throws RouteNotFoundError, UnauthorizedError {
        Request request
        -> Login
        -> Register
        -> Dashboard
    }
        

About

Author

Kyle Shank

Source Code

Github

License

Stop is open source and licensed under the MIT License.