A useful paradigm that should be considered when a stateful software system is to be designed is idempotence. Leaving aside its exact definition in mathematics and computer science, idempotence can be roughly described as a system characteristic whereby, calling an operation multiple times is equivalent to doing it exactly once.
For instance, setting the value of a variable X is an idempotent operation, because setting its value to be Y several times has the same effect as setting it just once. On the other hand, incrementing its value is not an idempotent method because the result depends on the number of times the operation is called.
Before delving into why you might want to have idempotence, let’s have a look at how it can be implemented. One way of doing it is like this:
Design a state machine
A state machine identifies every unique state that the system can exist in, with a clear indication of which states the system can transition to from any given state, and under what conditions.
Design a minimal API
An application programming interface is the gateway for external systems to interact with the service. We start by designing a minimal API, identifying all the methods that could change the state of the system. It is best not to clutter the API with non-critical methods at this stage.
Expressed differently, an API method represents a single transition in the state diagram. For example, a system could have states such as ReadyToPurchase and TaxAdded. The addTax() method, if successful, provides the transition from the first state to the second. This method can never be applied a second time, because by then the system has already moved on to the next state.
An important restriction on the API is that if the state has some attributes, they must be maintained meticulously and checked against incoming requests. Suppose the addTax() method takes a parameter called amount. If the method is called again with the same amount, then the expected response is success, since the system is indeed in a state that the function call was supposed to move it to. However, if amount is different, then the response should be a failure, or at least some kind of partial failure — otherwise, the client will assume that the new tax amount had been added. A state should never change its internal attributes once they have been set.
Transitions should be atomic
It goes without saying that transitions need to be atomic, so that multiple requests do not corrupt the state while the system is transitioning. This can be achieved easily by acquiring a lock on a mutex at the start of a transition, and releasing it on completion.
Naturally, it is a good idea to ask why we are putting in so much effort. While this may seem inordinately complex at first, you will soon discover that such a design actually simplifies the system tremendously when there is a great deal of concurrency. When there are a large number of processes or threads communicating with each other and the number of messages exchanged is huge, idempotence provides a guarantee of sorts about what the system can or cannot do. The state machine approach allows you to simply worry about the system in its current state, rather than the system as a whole. As a debugging technique, the developer simply needs to say, “This was the previous state A, and now it has moved into this new state B — what combination of parameters caused such a transition?” Idempotence makes this approach even more robust, ensuring that the system works in the face of message duplication and errors.
This robustness also allows the design to sacrifice a reliable communication layer in exchange for speed. For example, TCP (which has a larger overhead) can be replaced with UDP, since the application is resilient to message loss and duplication.