How MMORPGs helped us build OrderingStack?
Posted on 24/02/2020 by Łukasz Kaczmarczyk
Here we present a short story about drawing inspiration from unexpected sources when designing modern software architecture solutions.
It all started with our collaboration with AmRest - the largest independent company operating restaurant chains in Central and Eastern Europe. Since 1993, the Company has been developing a portfolio of excellent brands, such as KFC, Pizza Hut, Burger King and Starbucks based on franchise and joint venture cooperation. Currently, AmRest manages over 2,300 restaurants in the Quick Service Restaurants segment and the Casual Dining Restaurants segment in 26 countries including Poland, Russia, Hungary, Germany, and China.
The company decided to introduce Kiosks into its restaurant network. The implementation of the Kiosk is part of the company's overall policy for increased digitalization and process optimization, including previous projects, such as the mobile KFC app and “skip the line” function - allowing users to place an order before reaching the nearest restaurant and collecting their meal from the counter.
AmRest chose 3e Software House as a solid IT partner, proven in many previous ecommerce and online sales projects. Our advantages were an especially rich experience in Oracle’s Micros-enabled backend implementations supported by the proven high quality of our frontends. This combination of experience allowed 3e to fit seamlessly into the development and testing process, saving our client a lot of potential work related to the coordination of many contractors. Working with reputable partners, such as hardware manufacturer M4B, UX experts from Ribot and cash register manufacturer Posnet, we accepted the task of creating a top-class product that would supply more than 700 KFC restaurants in selected Central European countries with the prospect of further development and took responsibility for the technical side of the projects success.
A great experience and even greater dream
Our experience gained while creating the ordering platform for the AmRest network was invaluable. We have analyzed many POS systems, different solutions for both the stationary and mobile ordering of food.
We found that many POS networks and ordering platforms are face the same problems. The deeper they integrate with third party services such as loyalty programs or food aggregators such as Uber Eats and the more the product model and product promotion (upsell) changes, the more difficult certain software infrastructure solutions become.
The logic behind the service had to be copied and reproduced several times for each new application or feature. The clear conclusion for us was that it was much easier to keep the entire logic of the shopping cart and most of the product logic on the server side.
However, with heavy traffic (and let's make an appointment - in the fast-food network, for which most ordering platforms are designed, the number of orders is enormous) constantly referring to the server for every small operation (such as adding a sauce to a dish) could cause efficiency problems in some situations.
We thought it would be nice if there was an ordering platform where these problems do not exist.
Also, it would be ideal if such a platform also:
- Reached the level of quality/speed of the application offline so that when you click on the product, the reaction of the application is almost instantaneous (like clicking an attack button in an online game or sending a message in Messenger)
- Is fully omnichannel in which an order can be edited at the same time from different channels and even by several users (client, waiter, joint accounts)
- Could be easily integrated with loyalty platforms, food aggregators and apply non-trivial discount rules and have them working in the same way on each channel
Then we decided to build it.
During the creation of our new project we initially considered how to combine all the benefits, achieve the objectives and introduce conclusions based on experience from previous projects.
We noticed that an asynchronous communication model similar to online MMORPGs (such as World of Warcraft) could be a good solution.
In games of this type, several players have to cooperate in order to complete a quest. At the end of the quest a boss usually awaits the players, in the wake of its defeat,members of the party are awarded a prize - loot.
Each of the party members has their own role and their own specific task in the team. Each of them also performs real time actions that change the state of the game. For example the healer using a healing spell sends a message to the server about the change of the game state (healing a teammate), which is immediately sent to all participants of the game.
If we assume that the participants of the game are the clients, and also the waiter, the cook and the cashier, and also the state of the game is a current customer order status - this is exactly the technology we needed.
We're going to get the boss
After the initial attempts we found that such a model fits very well with what we want to build. Almost straight away we received a solution where you can edit the bill together as you edit the text in Google Docs. The prototyping of kitchen mechanisms (KDS) also came easily.
In the online multiplayer role-playing game there are long missions called instance. There is some dungeon with an event near the entrance, some clashes on the way down, a boss on the lowest level and a treasury to loot. Boss and his servants also affect the game in real time, although they do not belong to the party. Keeping the consistency with the initial metaphor, Kitchen staff can also influence the state of the game / orders in real time, although they do not directly interact with customers.
For example the customer's order could be changed in real time not only by the cook, but also paired with the whole team from the kitchen.The Grill operator can indicate that he has done their part of the job, the person responsible for the salad can let the rest of the team in the kitchen know that there will be a moment of delay, while the person responsible for completing the order can, for example, extend the estimated waiting time for food. All employees entitled to change the bill may influence the state of the game (order) in real time.
In the first draft implementations we stored the account balance in memory and received commands via the REST interface. Using this interface we initially transferred the account balance to the client application using the pull method, but quickly changed it to push via WebSocket.
This worked very well in the laboratory, but we began to wonder what the system should look like in its implementation.
Microservices and streaming
We decided from the beginning that the system must be as simple as possible, but at the same time based on microservices supporting individual application domains (domain driven). We wanted to do this because we would like to be able to share the applications development between several small teams responsible for different areas.
The argument in favour of this solution was also the idea of adapting the platform to the client. Adapting to the requirements of different customers is far more difficult if the solution is monolithic.
However, the key question in the case of microservices was: How to coordinate the state of the application between the individual components? We had thought about this for a long time and have come to the conclusion that what happens to the account from its creation to the last changes, presents itself well as an ordered sequence of events. We decided that some services need to keep the state (someone needs to know what the state of the bill, the warehouse, or the menu in the restaurant is).
An orderly sequence of events received only by one particular microservice will allow this sequence to be reproduced by this microservice. The conclusion was obvious: we need streaming. At this point, we needed a message broker that supports an orderly model of event sequences. This is how we started working with Apache Kafka.
Quite quickly we realized how helpful the Kafka Streams library is in maintaining the microservice condition. Thanks to it, we were able to keep local information, e.g. about open accounts, while having the same data in a compact queue in case you need to play it in a new instance of the application. It was also very easy to build a service that closes orders after a long period of inactivity.
In the end, the shape of the architecture we created looks like this:
Inside the Kafka streaming
The diagram above shows the basic data flow when working with orders. Each action taken by the user is sent as a status change command (e.g. "create a new account", "add an item to account X", "abandon account X", etc.). The system accepts such commands and queues them, with commands for the same account always falling into the same partition of the Kafka queue.
This way their processing order will be maintained and even when the command "add item to account X" will be sent immediately after "create account". - and so they will be done properly and the second will wait until the first one is fully processed. During processing each command is verified whether it can be executed for the current account balance. If not, an appropriate error message is sent to the user. Commands can also be supplemented during the validation process with reliable data (e.g. "add an item to the X account" refers to the product catalogue to verify if such a product exists and downloads a description of that product).
Both error messages and account updates are sent to the user via another, asynchronous, websocket channel. It is a mechanism that allows for the maintaining of the connection between the client application, the server and forwarding events when they occur (as opposed to POLL-type mechanisms, where the client periodically asks if something has changed).
It is worth noting that there may be feedback during the whole process and some mechanisms may also listen to the “topic” announcing changes in the account and react accordingly.
Examples of such processes:
- if the sum of payments covers the value of the order's total -> mark the order as completed (proceed further in the process to the account execution).
- if you add or remove a line, or change the amount of products in the line -> recalculate loyalty program discounts.
State changes and messages from the system go to the user asynchronously, so they do not have to be the result of the user's actions (they are not just a response to the user's actions) - we can initiate these changes inside the system. For example - it is possible to carry out the process of checking whether the user has started the order, but abandoned its edition. You can then send him a message that if he completes the order within 15 minutes, he will receive a discount. Another action of this type is to mark orders not edited for a long time as "abandoned" and close them. In this case the user will also receive a notification about the change of order balance!
On the basis of these experiences, the modern, holistic approach to technology and broad inspirations such as the system of information transfer between the server and the player in MMORPGs, we built OrderingStack - a ready to deploy, complete solution enabling self-ordering and payment in your restaurant.
Once implemented, its benefits also correspond to MMO games. The staff gets money, the customer gets food and the company gets growth and customer satisfaction. So gold, potions and fame - who needs anything else?