Serverless Asynchronous Multiplayer Reference Architecture
- Azure Function - Used to run small pieces of matchmaking logic. Note that when using a Consumption plan, Function definitions are stored in File Storage, meaning that you will have to create a Storage account. For optimal performance you should use a Storage account in the same region as the Functions.
- Azure Database for MySQL - Used to store the information as it is fast, lightweight, reliable and cost effective.
- Notification Hub - An easy-to-use and scaled-out push engine that allows you to send notifications to many platforms (iOS, Android, Windows, Kindle, Baidu, etc.).
- SignalR - Simplifies the process of adding real-time web functionality to applications over HTTP, allowing you to push data to connected device clients.
- Key Vault - The best service for managing secrets, including database connection strings.
This specific reference architecture showcases a simple serverless tic-tac-toe game.
In this reference architecture, a helper class (Data Client) will connect to and interact with the database, and the rest of the Functions will make use of it when needed. The Game Session class is used to run the turn with the information submitted by the player, and for calculating the winner. There will be 3 different action events supported: forfeit (to give up on a game), addPlayer (to join a player to a game session) and takeTurn
Step by step
Create a new game session
- The device client formats any game settings selected by the player and sends the start game session event to the backend, then awaits a response.
- The backend receives the command to start a new game session. First of all it tries to find an existing game session that matches the player settings.
- Assuming a suitable game session is not available for matchmaking, a new game session object is created.
- A new durable Orchestrator Function is created.
- The durable Orchestrator Function reads the game session object and waits until at least 2 players have joined the game session.
- Another player with the same settings as those selected by the first player sends a start game session event to the backend.
- The backend receives the command and tries to find an existing game. In this case it finds the game session previously created.
- The durable Orchestrator Function receives the addPlayer event and stops waiting as the 2 players have joined the game session.
- The durable Orchestrator Function officially starts the match, setting the game state to in progress and randomly selecting one of the players to start. In a nutshell, the Orchestrator is responsible for executing the game logic and updating the game state.
- The durable Orchestrator Function triggers an operation to persist the data into the database. It leverages the data client helper class to connect to the database to persist the data.
- The durable Orchestrator Function runs the game session logic and returns that it's the turn of the next player.
- It queues notifications to the player or players based on the game conditions (it's some elses turn, someone won, someone forfeited, etc).
- The durable Orchestrator then invokes itself with the new game state, and waits for the next event to be received.
- The device client receives the notification, including the unique identifier of the Orchestrator Function managing the game session.
- The device client sends the load game session event to the backend, including the unique identifier of the durable Orchestrator received in the notification.
- The backend receives the command to load the game session and returns the details of the game session to be displayed by the device client.
- The player submits an X or a O directly to the durable Orchestrator Function and ends the turn.
Request player games list
- The device client submits the get list of sessions command to the backend.
- The backend handles the request by querying the database, then sends the the data to the device client. Consider limiting the number of results returned by the query and ordering them.
- The device client receives the backend response and uses the data it contains to populate the pertinent UI.
Exercise for the reader
Consider fronting the database with a cache or scale up the database to avoid exhausting the connections to the database.
Do not hard-code the database connection string into the source of the Function. Instead, at a minimum, leverage the Function App Settings or, for even stronger security, use Key Vault instead. There is a tutorial explaining how to create a Key Vault, how to use a managed service identity with a Function and finally how to read the secret stored in Key Vault from a Function.
Additional resources and samples
Trivia game using durable Functions and Azure SignalR service
Though it's not entirely asynchronous (it runs on a 20 seconds timer), you can find the implementation of a trivia game built on durable Functions and Azure SignalR Service at this link.
Here's how it works:
- The trivia game hoster starts the game, invoking the HttpStartSingle Azure Function, either upon deployment or via a web request.
- That Azure Function kicks off the TriviaOrchestrator Durable Azure Function.
- Players join the game. The device client from each player running the app retrieves the Azure SignalR Service hub details from the backend. The backend receives the request via the SignalRInfo Azure Function. A token is generated by the function binding by using the key in the connection string, then it is sent to the device client. The device client sets the two events from the Azure SignalR Service that is going to listen to: newClue and newGuess.
- On the backend, the TriviaOrchestrator Durable Azure Function calls the GetAndSendClue Durable Activity Azure Function.
- The TriviaOrchestrator Durable Azure Function sets a timer to call itself every 20 seconds, which is the amount of time the players have to respond to the trivia clue.
- The GetAndSendClue Durable Activity Azure Function pulls a trivia clue from the jservice.io service.
- Then the trivia clue is broadcast via Azure SignalR Service to all connected users with the target newClue.
- Players receives the trivia clue from the Azure SignalR Service, and come up with their answer.
- The backend receives the player responses via the SubmitGuess Azure Function.
- This Azure Function calculates if the answers submitted by the players are correct or not. Then the outcomes are broadcast via Azure SignalR Service to all connected users with the target newGuess.
- The device client receives the response from Azure SignalR Service and updates the number of correct or incorrect guesses.
If you don't have an Azure subscription, create a free account to get started with 12 months of free services. You're not charged for services included for free with Azure free account, unless you exceed the limits of these services. Learn how to check usage through the Azure Portal or through the usage file.
You are responsible for the cost of the Azure services used while running these reference architectures. The total amount will vary based on usage. See the pricing webpages for each of the services that were used in the reference architecture:
- Azure Function
- Azure Database for MySQL
- Azure Notification Hubs
- Azure SignalR Service
- Azure Key Vault
You can also use the Azure pricing calculator to configure and estimate the costs for the Azure services that you are planning to use.