The Missing State In Your State Machine
The situation in which an item is acknowledged but not yet stored is a perennial problem in state machines
The state machine you built is almost certainly incomplete.
I can see the whiteboard in your office, or the Miro board you had prepared, full of details and notes. I can visualize the transitions, the edge cases. But I can also see what went wrong, in a very subtle way.
Let me tell you what I see.
Let’s say we want to implement a service that handles the process of placing an order. This order goes through multiple stages during its lifetime. Initially it is pending, then goes to confirmed and paid. Finally it ends up as either completed or canceled.
— Ürgo Ringo, Implementing entity states as separate classes
State machines are unexpectedly one of the most difficult components of the domain logic in any domain system. They’re meant to represent the lifecycle of a given entity under all possible interactions with users, and they almost always seem to be missing something crucial, and impossibly hard to introduce retrospectively.
Which is why I’ve been very vocal against them in the past.
That said, Ürgo Ringo’s article on implementing state machines, not as enums, but as separate classes, has made a profound influence in how I build software systems.
And that’s because I resonate with the idea that the compiler can help you become aware of gaps in the way you represent an entity’s lifecycle before you go live with the changes.
This, alongside Alexis King’s Parse, Don’t Validate, made me realize that in order to build a more robust approach to how state machines are implemented in the wild (full of switch statements and guard clauses), I had to treat entities as completely different types as they evolve.
If you do this, you’ll quickly realize something: identifiers are optional, in a very weird way.
Let’s say, just like in Ürgo Ringo’s article, that we’re implementing a system to place orders, Amazon style. An order may have creation time, items, nothing out of the ordinary. And it has an order_id.
Is that id optional?
On the one hand, no, not really. Orders must be uniquely identified in the system, and you’d benefit greatly from not having to check if the damned id is null or not every time you check its value, or cache it, or use it in any other way. It wouldn’t make sense to separate orders into different classes to avoid if statements, only to reintroduce them in the silliest way possible.
On the other hand, you may need to pass an order instance in order to create the record in the database, but until you do, there’s no order_id you can assign to that entity.
So you probably bit the bullet and made order_id optional.
That was a mistake.
I’m Alvaro Duran, and this is The Payments Engineer Playbook. You’re already subscribed to free newsletters that “teach” you how to get a job as a software engineer.
But you don’t want to get a job; you already have one. What you want is to learn how to be great at your job. Especially as a payments engineer, where stakes are sky-high, and the margin for error is razor-thin.
In The Payments Engineer Playbook, we investigate the technology that transfers money. All to help you become a smarter, more skillful, and more successful payments engineer. And we do that by cutting off one sliver of it and extracting tactics from it.
In today’s article, we’re going down into the weeds and ask how can I make an entity’s identifier non-optional, without losing the ability to pass that entity to the database?
This time, and by popular demand, I’m going to show you some code. It’s Go, but the takeaways should be seamlessly portable to any language, even Java.
Here’s what you can expect in today’s article:
How to use interfaces and DTOs instead of inheritance
Type assertions as a way to add information to the compiler
A practical example on how to use interfaces to solve a state-machine-like transition without state machines
Enough intro, let’s dive in.





