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.

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 also yield a single value and return it. Value states are utilized as dynamic properties.

    GetUser <- User {
        uint64 id
    }

    Profile {
        uint64 user_id
        User user <- GetUser(id: user_id)
        -> 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.

    CreatePost {
        Post post
        >> PushNotification
        -> 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 globally or within a state.

An enumeration can be defined globally and can be used within multiple states.

    enum Method {
        GET
        POST
        DELETE
        PUT
    }

    Request {
        Method method
        String path
    }

    ErrorLogMessage {
        Method method
        String path
        String error
    }
        

An enumeration can also be defined and used only within the context of a single state.

    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 value state GetUser with the user_id property which gets mapped to the id property of GetUser. The Profile state is valid after it has gathered all of its required and dynamic properties successfully.

    GetUser <- User throws UserNotFoundError {
        uint64 id
    }

    Profile {
        uint64 user_id
        User user <- GetUser(id: user_id)
        -> 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
    }
        

Queue States

Queue states are a special kind of state that cannot be transitioned to. Queues can only be added to with the guarantee 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:

    CreatePost {
        Post post
        >> PushNotification
        -> Redirect
    }

    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.

A runtime implementation 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.