Rethinking the Command Pattern: Moving From State to Transactional Ground Truth
Beyond the State: Redefining Application Truth
Most developers default to treating current state—like an account balance—as the ultimate source of truth. It is a mess to manage when requirements scale. If you rely solely on state, implementing a transaction history or complex undo/redo logic becomes a redundant nightmare. You end up maintaining a list of past actions alongside the current state, and the moment they drift apart, your system loses integrity.
By shifting the ground truth from the state to the history of transactions, you fundamentally change how the application breathes. In this model, the state is no longer a fixed value; it is a derived result. This is not just a semantic change. It transforms your implementation from a set of instructions into a permanent .
Prerequisites and Tools
To follow this tutorial, you should be comfortable with and basic object-oriented design. We utilize the following tools:
- Python 3.10+: For data classes and type hinting.
- GitHub Repository: The provides the starting point for these modifications.
- Protocols: Used to define the structural interface for our command objects.
Refactoring for a Transactional Ledger
To implement this, we first modify the . Instead of executing commands immediately, we register them in a list called a ledger. We also replace complex undo/redo stacks with a single current index pointer.
@dataclass
class BankController:
ledger: list[Transaction] = field(default_factory=list)
current: int = 0
def register(self, transaction: Transaction):
# Delete any "future" redo history after current pointer
del self.ledger[self.current:]
self.ledger.append(transaction)
self.current += 1
def undo(self):
if self.current > 0:
self.current -= 1
def redo(self):
if self.current < len(self.ledger):
self.current += 1
This architecture makes undoing an action as simple as moving an integer pointer backward. No state is actually reversed; we simply decide which transactions to ignore during the next calculation.
Computing State as a Cache
Since the balance is no longer the ground truth, we treat it as a balance_cache. This signals to other developers that this value is volatile and can be reconstructed at any time. To find the current balance, we iterate through the ledger up to the current pointer.
def compute_balances(self):
# First, reset all account caches to zero
self.bank.clear_all_caches()
for transaction in self.ledger[:self.current]:
transaction.execute()
Practical Applications
This pattern isn't limited to banking. Non-destructive editing tools like use this extensively. They never modify your original video file; they store a series of transformations (commands) and render the result as a "cache." Similarly, 3D modeling tools apply effects on top of base meshes, allowing you to toggle modifications without ever losing the original data.
Syntax Notes and Best Practices
- Method Renaming: Change
executetoregisterin the controller to reflect that the command is being logged, not necessarily immediate state-altering. - Pointer Logic: Always clear the "future" part of the ledger when a new transaction is registered after an undo. Failing to do so creates a branched history that the simple pointer cannot resolve.
- Performance: For systems with thousands of transactions, implement "snapshot" points where you store the computed balance at a specific index to avoid re-calculating the entire history every time.
- 17%· products
- 17%· concepts
- 17%· concepts
- 17%· products
- 17%· concepts
- 17%· languages

How To Make The Command Pattern More Flexible With One Simple Change
WatchArjanCodes // 17:43
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!