Most developers know that microservices should be stateless, meaning that any instance of a microservice should...
be able to field a request and serve up the same response. Stateless operation is essential for scalability because scaling means the instance that receives the second request in a sequence might not be the same as the instance that got the first. In addition, if multiple applications use the same microservice, then the applications' usages could collide and create errors or contaminate databases. To make microservices live up to the promise of composability and scalability, developers must control state, even in a so-called stateless service.
Business processes are almost always transactional, meaning they update the record of some commodity, including materials, such as goods received or inventory, and money, like accounts payable or receivable. While a single message is typically valid in the context of the transaction, stateless microservices don't have context. Therefore, transaction processing is often multistep, with several communications sent to receive a request, edit it, check the transaction against current balances, relieve the balance and sometimes generate a message alerting to a low or high condition.
State control isn't necessary when microservices act in truly stateless feature implementation. Microservices are elements of a multicomponent application. When you lay out your component structure, identify any tasks and features that don't require context or state. Use microservices for these elements without regard to state control. However, there's a deeper implication to state that you may have to consider. If a microservice stores the intermediate results of a calculation internally and if it's run again at the same time by another application that shares the microservice, the second set of results can overlay the first and create a wrong outcome. When you compose multiple services from a common set of microservices and those services could use the same set of microservices simultaneously, either design the applications to queue requests to prevent concurrent execution, or use functional programming practices that store data outside of the microservice.
Front-end or back-end state control?
There are two approaches to control state in stateless microservices. Either let the transaction logic that invokes the microservice in the front end handle state, or delegate the task to a back-end database. Each approach has pros and cons, but both essentially turn an application's state into a parameter for microservices to work with.
Front-end state control is suitable if the transactions are handled by a web or mobile GUI/app, and this front-end server controls the sequence of steps being taken. Sometimes, the front-end process can make truly stateless requests to microservices. When it doesn't, the front end can provide the state as part of the data it sends to the microservice, and the microservice can then adjust its processing based on that state. This approach doesn't add any complexity or processing delay to the app's design.
Back-end state control is the more complex approach to take with stateless microservices, from developmental and operational perspectives. With back-end state control, the microservice maintains a database of state information, and it accesses that database when it has to process a message. If the microservice supports numerous transactions, a problem arises because it must determine which back-end database record corresponds with the current message. Sometimes, a transaction ID, timestamp or other unique identifier is provided for logging and can be used for state control as well. Otherwise, the front-end processes must provide some ID, which is complicated.
Where is the back-end state database? That's the primary operational problem with back-end state control. Each microservices instance can have only one database. Otherwise, sharing and load balancing do not work; the next message could go somewhere else. A shared-state database is the only answer, but it engenders access latency and a network delay associated with making the state request and getting a response. If the database fails or the connection is lost, microservices state control fails.
The location and reliability of a back-end state database are critical. The biggest challenge occurs when a microservice scales, because the new instances of the microservice could be spun up far from the earlier instances over the network. As a result, the network delay associated with each instance will be different, affecting application performance. It is complicated to relocate a back-end state database, unless the microservices can be stopped while the move happens. If a microservice gets restored, you will lose current state data. Distributed transaction processing techniques help maintain a backup state database, but that further increases network delay.
Despite the fact that back-end state control often gets the nod in developer material, the better approach is front-end state control, unless the circumstances of the application or set of applications make it impossible. Front-end processes almost always know the context of their requests and can frame microservices' access to be truly stateless or at least pass the state along to a microservice without much additional overhead.