The “Tick” Pattern — A Solution for Temporal Problems in State Machines

How to automate workflows that wait for external and time conditions without exploding complexity

Marco Pfeiffer
Better Programming

--

Photo by Campaign Creators on Unsplash

So you have to program stuff like:

  • Send a reminder email after one day
  • Process an order after the delivery date has been reached
  • Mark a document as signed based on the status of an external service

These processes need some form of organization since you can’t just sleep your process for an hour to continue a job. This is where the Tick pattern comes in.

The Tick Pattern

You might already know the term “tick.” Wikipedia describes it pretty well:

A single update of a game simulation is known as a tick […]

We usually aren’t programming games in Symfony, but we can still borrow this concept and use the catchy name.

What Is the Plan?

I suggest building a command/task/job that performs “the tick.”

“The tick” is just a periodic check if a state machine transition is possible. That allows you to define all sorts of conditions (date, inventory, user confirmation) in plain code without having to find ways to execute your logic once the condition is met.

Rough process of performing a tick

Here is a list of reasons you might want to handle tasks like this:

  • One entry point: You only need one cron job compared to a cron job for every async task.
  • Simpler tooling: You only need to build monitoring and logging around one asynchronous tasks
  • Robustness. If something fails, it’ll just rerun on the next tick. Think about unavailable apis or SMTP servers. With this pattern, it’ll just retry.
  • Changeable: Changing delivery dates directly in the database or changing delays in a new version. All those changes are instantly reflected without migrations or special code paths

But, the pattern has some problems:

  • Scaling: The more entities there are, the longer the tick will take.
  • Wasteful: Every entity in some states will be checked on every tick.

Defining the Tick in Metadata

I’ll now get specific to the symfony workflow component, but you can probably adapt it to other state machines.

You want to be able to easily read the behavior in your application. State machine definitions already help, but did you know that you can add arbitrary metadata to a symfony workflow definition?

transitions:
deliver:
from: new
to: delivering
guard: subject.checkPreconditions()
metadata:
on_tick: true

It really is just arbitrary data, but that means we can describe new behavior for usage later. In this case, I’ll define on_tick, which is just a flag for us that we want to check later.

I also used the guard property to define a condition on when the state is allowed to change. This is nicely readable but fairly limited, you should use Guard Events instead.

Building the Tick Service

You’ll want to easily perform a tick, so let’s build a small Service to do it. Here’s the code:

With this simple service, you can just run $service->tick($order) to run pending transitions. This can be useful right after creating the entity to start a process immediately. Or not, if there is a condition currently blocking it.

Things that could also make sense in this service:

  • A dry run method like simulate so you can check if there is something pending without actually doing it
  • A method to return all transition blockers so you can give explanations why an Order is stuck

Building the Cron Command

We’ll now need a command to trigger ticks from outside our programs world, usually a cron job. But you’ll also want to be able to manually trigger single ticks, so the CLI should be a bit fancy.

The command has to be specific to the underlying database, so I create the tick command for my example of an Order, but you can adjust it to whatever you like or even let it run through multiple entity types. Here’s more code:

With this command, you can now run order:tick and all transitions marked as on_tick: true will be executed if they aren’t blocked by other conditions.

One small trick I use here is injecting the state machine directly using named autowiring with the argument $orderStateMachine to explicitly inject the state machine with the name order. You usually get your state machines form the Registry service, but that has no way of accessing the state machine without having the object. And here, we want the state machine first to create a list of states, that have an on_tick transition.

This command also accepts a list of ids as an argument. That way, you can run a tick on a specific order. This is really useful for testing.

Things you might want to improve yourself:

  • Prioritizing of specific entities (eg. process older orders first)
  • Loading entities/orders in batch to improve throughput
  • You could try to run through the id list using multiple processes. Either by spawning processes yourself or by piping all id’s though echo [id list] |xargs -P4 -n100 php bin/console order:tick.

Now It’s Your Turn

Did I miss anything? Did I help you? Tell me in the comments.

--

--

Full-Stack Web Developer for hauptsache.net. I document my findings on Symfony, TYPO3, React. See more at: www.marco.zone