Player accounts: 100k, growing by 20% per year
Simultaneous games: 5k, growing by 20% per year
Daily chat messages: Average 20 messages per game, for 100k messages per day, growing by 20% per year
Storage for account info 100k times 100KB (include name, email, account ID, and image) -> 10GB, growing by 20% per year
Storage for games increases by 5k times 1KB (include each move and chat message ID) -> 5MB per day
Storage for chat messages increases by 100k times 100B -> 10MB per day
POST /api/v1.0/player
{"name": string, "email": string, "user_tag": string}
If all fields a valid and user_tag isn't already taken:
200 {"player_id": uuid, "name": string, "email": string, "user_tag": string}
Else: 400 Bad Request
GET /api/v1.0/player/{player_id}
200 {"player_id": uuid, "name": string, "email": string, "user_tag": string, ... (player stats)}
POST /api/v1.0/player-image/{user_id}
Content-Type: {MIME type for image}
{binary data for image}
If MIME type is valid image type and binary data is valid for the MIME type:
200 {"player_id": uuid, "image_size": 123}
Else: 400 Bad Request
GET /api/v1.0/ws/{user_id}
If there is an existing web socket connection for player_id, return that. Otherwise, set up a new web socket connection and return that.
---
All communications with the Web Socket connection will be with JSON objects, allowing for flexibility and extensibility in operations
Web Socket Operations for starting a game
Receive: {"action": "start_game", "game_id": uuid, "player_color": enum, "opponent_id": uuid}
Web Socket Operations for turn management
Receive: {"action": "player_turn", "active_player": uuid}
Web Socket Operations for making moves
Send: {"action": "make_move", "game_id": uuid, "old_position": string, "new_position": string}
Attempt to make the given move. If the positions refer to players King and the King is in the 1st rank and the move is more than 1 space along the 1st rank, it is considered to be a castling
If the move is valid:
Receive: {"action": "accept_move", "game_id": uuid, "player_id", {player_id}, "old_position": string, "new_position": string}
Else:
Receive: {"action": "reject_move", "game_id": uuid, "old_position": string, "new_position": string, "explanation": string}
Web Socket Operations for resigning
Send: {"action": "resign", "game_id": uuid}
Receive: {"action": "game_ended", "game_id": uuid, "winner": {opponent_id}, "explanation": "resigned"}
Web Socket Operations for proposing draw
Send: {"action": "propose_draw", "game_id": uuid}
If opponent rejects draw
Receive: {"action": "opponent_rejected_draw", "game_id": uuid}
else
Receive {"action": "opponent_accepted_draw", "game_id": uuid}
Receive: {"action": "game_ended", "game_id": uuid, "winner": None, "explanation": "draw"}
Web Socket Operations for opponent proposing draw
Receive {"action": "opponent_proposed_draw", "game_id": uuid}
If we accept:
Send: {"action": "accept_draw", "game_id": uuid}
Else:
Send {"action": "reject_draw", "game_id": uuid}
Web Socket Operations for game clock
Receive: {"action": "update_clocks", "game_id": uuid, "player_clock": int, "opponent_clock": int}
If player's clock runs out
Receive: {"action": "game_ended", "game_id": uuid, "winner": {opponent_id}, "explanation": "Clock expired"}
If opponent's clock runs out
Receive: {"action": "game_ended", "game_id": uuid, "winner": {player_id}, "explanation": "Clock expired"}
Web Socket Operations for other game ending scenarios
Receive: {"action": "game_ended", "game_id": uuid, "winner": {player_id}, "explanation": "Check Mate"}
Web Socket Operations for recovering state of game
Send: {"action": "recover_game_state", "game_id": uuid}
Receive: {"action": "receive_game_state", "game_id": uuid, "state": string}
The string holds the position of each piece on the board and the active player
We will have a SQL Database (MySQL) that holds player and game information
table Players
player_id: uuid -- Primary Key
username: string UNIQUE -- Index
email: string
rank: int
table Games
game_id: uuid -- Primary Key
player_1: uuid -- Index
player_2: uuid -- Index
player_1_clock: int
player_2_clock: int
board_layout: string (representation of positions of all pieces on the board)
outcome: enum
We will have a NoSQL Database (Cassandra) that holds all game activity (moves, chat, draw invitations)
table Moves
game_id: uuid -- Primary Key
time: timestamp -- Index
action: enum (move, chat, etc.)
start_position: string (relevant for standard chess moves)
end_position: string (relevant for standard chess moves)
message: string
We have an API Gateway that serves as a Load Balancer between service nodes and as a Rate Limiter (to defend against bad actors)
There is a Players Service that handles creation and modification of player details
There is a Connections Service that serves Web Socket Connections when a player wishes to play a game. These web sockets handle all communication for starting a game, making moves, sending chat messages, and ending the game
There is a Games Service that responds to incoming Web Socket Traffic (accepting game invites, making moves, sending chat messages) and routes outgoing Web Socket Traffic. When a game is started, it creates an entry in the Games table in MySQL. As moves are made, the Games Service updates the Games table with remaining clock, player turn, and board state. The Games Service also determines when a game has been won or drawn.
The Matchmaker Service attempts to find a suitable opponent for a player requesting a game, using player rank--and the requested game characteristics (e.g. clock approach)--to find a good match
When a player attempts a move, it is validated with the Move Validation Service (which holds the logic for legal chess moves)
There are caches in front of both databases to improve performance
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
Explain any trade offs you have made and why you made certain tech choices...
Try to discuss as many failure scenarios/bottlenecks as possible.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?