Adam Kewley

State Machines in ReactJS

I’m currently implementing job resubmission in Jobson UI and found that state machines greatly simplify the code needed to render a user workflow.

Background

A large amount of Jobson UI’s codebase is dedicated to dynamically generating input forms at runtime.

Generating the relevant <input>, <select>, <textarea>s, etc. from a Jobson job spec is fairly easy (see createUiInput here) but became increasingly complex after adding job copying because extra checks needed to be made:

  • Is the job “fresh” or “based on an existing job”?
  • Was there a problem loading the existing job?
  • Did the existing job load OK but can’t be coerced into the live version of the job spec?
  • Did the user, on being informed of the coercion issue, decide to start a fresh spec or make a “best attempt” at coercion?
  • etc.

Each of these conditions are simple to check in isolation but, when combined, result in delicate state checks:

render() {
  if (this.state.isLoadingSpecs)
    return this.renderLoadingSpecsMessage();
  if (this.state.errorLoadingSpecs)
    return this.renderSpecsLoadingError();
  else if (this.state.isLoadingExistingJob)
    return this.renderLoadingExistingJob();
  else if (this.state.errorLoadingExistingJob)
    return this.renderErrorLoadingExistingJob();
  else if (this.state.isCoercingAnExistingJob)	
    // etc. etc.
}	

These checks were cleaned up slightly by breaking things into smaller components. However, that didn’t remove the top-level rendering decisions altogether.

For example, the isLoadingSpecs and errorLoadingSpecs checks can put into a standalone <SpecsSelector /> component that emits selectedSpecs. However, the top level component (e.g. <JobSubmissionComponent />) still needs to decide what to render based on emissions from multiple child components (e.g. it would need to decide whether to even render <SpecsSelector /> at all).

State Machines to the Rescue

What ultimately gets rendered in these kind of workflows depends on a complex combination of flags because only state, rather than state and transitions are being modelled. The example above compensates for a lack of transition information by ordering the if statements: isLoadingSpecs is checked before isLoadingExistingJob because one “happens” before the other.

This problem—a lack of transition information—is quite common. Whenever you see code that contains a big block of if..else statements, or an ordered lookup table, or a switch on a step-like enum, that’s usually a sign that the code might be trying to model a set of transitions between states. Direct examples can be found in many network data parsers (e.g. websocket frame and HTTP parsers) because the entire payload (e.g. a frame) isn’t available in one read() call, so the parser has to handle intermediate parsing states (example from Java jetty).

State Machines (SMs) represent states and transitions. For example, here’s the Jobson UI job submission workflow represented by an SM:

From a simplistic point of view, SMs follow simple rules:

  • The system can only be in one state at a given time
  • There are a limited number of ways to transition to another state

I initially played with the idea of using SMs in ReactJs UIs after exploring SM implementations of network parsers. I later found the idea isn’t new. A similar (ish) post by Jeb Beich has been posted on cogninet here and contains some good ideas, but his approach is purer (it’s data-driven) and is implemented in ClojureScript (which I can’t use for JobsonUI). By comparison, this approach I used focuses on using callbacks to transition so that individual states can be implemented as standard ReactJS components. In the approach:

  • A state is represented by a component. Components can, as per the ReactJS approach, have their own internal state, events, etc. but the top-level state (e.g “editing job”) is represented by that sole component (e.g. EditingJobStateComponent)

  • A component transitions to another state by calling a callback with the next “state” (e.g. transitionTo(SubmittingJobStateComponent)).

  • A top-level “state machine renderer” is responsible for rendering the latest component emitted via the callback.

This slight implementation change means that each component only has to focus on doing its specific job (e.g. loading job specs) and transitioning to the next immediate step. There is no “top-level” component containing a big block of if..else statements.

Code Examples

A straightforward implementation involves a top-level renderer with no decision logic. Its only job is to render the latest component emitted via a callback:

export class StateMachineRenderer extends React.Component {

  constructor() {
    const initialComponent =
      React.createElement(InitialStateComponent, {transitionTo: this.handleTransition.bind(this)});

    this.state = {
      component: initialComponent,
    };
  }

  handleStateTransition(nextComponent) {
    this.setState({component: nextComponent});
  }

  render() {
    return this.state.component;
  }
}

A state is just a standard component that calls transitionTo when it wants to transition. Sometimes, that transition might occur immediately:

export class InitialState extends React.Component {
  componentWillMount() {
    const props = {transitionTo: this.props.transitionTo};

    let nextComponent;
    if (jobBasedOnExistingJob) {
      nextComponent = React.createElement(LoadExistingJobState, props, null);
    } else {
      nextComponent = React.createElement(StartFreshJobState, props, null);
    }
    this.props.transitionTo(nextComponent);
  }
}

Otherwise, it could be after a set of steps:

export class EditingJobState extends React.Component {
  // init etc.
  onUserClickedSubmit() {
    this.props.api.submitJob(this.state.jobRequest)
      .then(this.transitionToJobSubmittedState.bind(this))
      .catch(this.showErrors.bind(this))
  }
	  
  transitionToJobSubmittedState(jobIdFromApi) {
    const component = React.createElement(JobSubmittedState, {jobId: jobIdFromApi}, null);
    this.props.transitionTo(component);
  }
}

Either way, this simple implementation seems to work fine for quite complex workflows, and means that each components only contains a limited amount of “transition” logic, resulting in a cleaner codebase.

Conclusions

This pattern could be useful to webdevs that find themselves tangled in state- and sequence-related complexity. I’ve found SMs can sometimes greatly reduce overall complexity (big blocks of if..else, many state flags) at the cost of a little local complexity (components need to handle transitions).

However, I don’t reccomend using this pattern everywhere: it’s usually easier to use the standard approaches up to the point of standard approaches being too complex. If your UI involves a large, spiralling, interconnected set of steps that pretty much require a mess of comparison logic though, give this approach a try.

Cheeky Hackers

I’ve been running several webservers behind custom domains for a while now (plateyplatey.com, textadventurer.tk, and this site) and it never ceases to amaze me how cheeky bots are getting.

For example, certbot recently started complaining that textadventurer.tk’s TLS certificate is about to expire. That shouldn’t happen because there’s a nightly cronjob for certbot renew.

On SSHing into the server I found an immediate problem: the disk was full. Why? Because some bot, listed as from contabotserver.net decided to spam the server with >10 million requests one afternoon and fill the HTTP logs. Great. Looks like I’m finally going to implement some log compression+rotation.

Then there’s the almost hourly attempts to find a PHPMyAdmin panel on my sites. That one always surprised me: surely only a small percentage of PHP sites are misconfigured that badly? Lets look at the stats:

Percentage of websites using PHP

Even if 1 % of them are misconfigured, we’re doomed.

Jobson: Now in 2D

I recently made screencasts that explain Jobson in more detail. The first explains what Jobson is and how to install it. Overall, Jobson seems well-recieved. The first video seems to be leveling off at around 2700 views and Jobson’s github repo has seen a spike in attention.

Will other teams start adopting it or not? Only time will tell.

Jobson: Webify CLI Applications

This is a post about Jobson, an application I developed and recently got permission to open-source along with its UI.

Do any of these problems sound familiar to you?:

I’d like my application to have a UI.

I want to trace my application’s usage.

I want to share my application.

They’re very common problems to have, and are almost always solved by building a UI in a framework (e.g. Qt, WPF), sprinkling on usage metrics, and packaging everything into a fat executable.

That development approach is becoming challenging to do well nowadays because clients are more likely to use a mixture of OSs and 3rd-party resources. Therefore, new projects tend to need to choose between several options:

  • Develop a CLI application. The easiest thing to develop in many situations. However, they can be challenging to use and have the usual client-side distribution problems (installers, etc.).

  • Develop a UI application. Easier to use use. However, they’re usually less flexible for developers to use and are more challenging to develop.

  • Develop a web API. Has similar flexibility to client-side CLI applications but doesn’t have the distribution issues. However, web APIs are challenging to develop because you need to deal with networking, sysadmin, setting up a domain, authentication, etc.

  • Develop a full (API + UI) webapp. Easy to use and flexible (because of the API). However, webapps have all the complexities of the above and more: you effectively need to develop a full web API and a working UI.

When scoped in this (biased) way it’s clear that webapps are ideal vehicles for delivering the full “product” but CLI applications are ideal for ease-of development and flexibility.

It’s clear that developing a method for turning CLI applications into webapps would be valuable. It would enable developers to rapidly develop and roll out applications. That’s what I explored with Jobson: a web server that turns CLI applications into webapps.

Jobson’s Approach

Jobson has now been working in a production environment for a few months now and has proven to be an effective vehicle for delivering new platforms. However it cannot turn any application into a webapp. That would be tough: the potential “inputs” (applications) and “outputs” (webapps) are far too varied.

Jobson’s primary limitation is that the only applications it handles are batch applications that: a) start, b) take typical inputs, c) write typical outputs, and d) end. Most applications follow this model (e.g.echo, gcc, nmap, and ls).

With that limitation in place, Jobson could then be designed with simple principles in mind:

  • Input applications are explained to Jobson via “specs” (at the moment, YAML files). The applications themselves should not need to be changed in order to suit Jobson.

  • Jobson uses the specs to host a standard HTTP REST API which can be used by all general programming languages and utilities (e.g. curl jobson-server/v1/jobs)

  • The API is regular and predictable: job specs should always create the API you’d expect.

  • Jobson’s authentication uses standard protocols (e.g. HTTP Basic) that can be integrated into a larger systems easily.

  • (Default) persistence uses plaintext files on the filesystem, which allows for transparent debugging and administration.

  • Persistence can be swapped for other implementations (e.g. a MongoDB implementation)

  • Job execution itself uses the underlying operating system’s fork(2), and exec(2) syscalls, which allows Jobson to run any application the host OS can run.

  • By default, Jobson has no external dependencies beyond Java. It should be bootable from a barebones linux server with minimal configuration.

Overall, this design means that Jobson can webify almost any application very quickly. I could webify a major CLI tool in less time than it took to write this post, resulting in a web API and UI for that tool.

Why It’s Useful

Here are some typical development problems Jobson could help out with:

Problem: You’ve got a cool application idea you want to share

Without Jobson:

  • Develop the idea
  • Develop all the crap necessary to share that idea (download links, websites, databases, etc.)
  • Share link

With Jobson:

  • Develop the idea
  • Write a Jobson spec
  • Share link

Problem: You’ve got a toolchain (e.g. a pentesting toolchain) that has hard-to-remember commands. You want to streamline that toolchain such that you can run and queue multiple commands.

Without Jobson:

  • Develop a UI around the tools, dealing with the usual development headaches
  • Use the tool

With Jobson:

  • Write a few small scripts around the tools (if necessary)
  • Write a job spec
  • Use the Jobson UI

Problem: You want to automate data requests at work, but there’s a lot of flexibility in the requests (FYI: this is why Jobson was made).

Without Jobson:

  • Write an application to handle typical data requests. The application will have to deal with request queues, request structuring, etc.
  • Find that end-users suddenly want to make different requests
  • Redevelop, etc.

With Jobson:

  • Write short scripts for handling a data request
  • Host it with Jobson, which automatically deals with all the other stuff
  • Find that end-users suddenly want to make different requests
  • Add a new script + spec into Jobson

Overall, I believe this approach to developing a job system is extremely flexible and much easier to work with. Jobson abstracts all the typical job system faff (auth, APIs, queues, etc.) away from what’s actually important (an application that does something), resulting in a much cleaner system.

The next steps with Jobson are to streamline installation (by implementing installers), add a landing+documentation page, and start making tutorial videos for it. Watch this space =)

Textadventurer Has Actual Games Now

I’ve been distracted by other things going on, but I finally managed to spend an evening or two adding actual games into textadventuer.

The following games were added:

  • Droids (a text adventure game), python3, I found here
  • DUNGEON (Zork 1), native C application, One of the earliest interactive fiction computer games ever made (wiki). I compiled the open-source port of it for Textadventurer
  • Descent Into Evil, python2, I found it on Tyler Kershner’s blog
  • Zork-py, python3, A fan’s interpretation of Zork (above) that I found here
  • Python Text Adventure Game, python2, someone’s collage project that I found here
  • little-dungeon, python2, posted on reddit with accompanying source

One thing the game search did for me was find similar platforms. I should’ve looked before hacking away at textadventuer because some of those platforms (e.g. http://textadventures.co.uk/) are very well made. Some even use clever tricks like emulating DosBox in the browser. However, I’m yet to come across a platform that can run any application via a browser (textadventuer’s “USP”, if you will), so hopefully other developers will find the server and UI source helpful if they have an idea along those lines.