Guess the next price of BTC in USD... with a twist – it is a multiplayer game!
The price of the BTC in USD refreshes every 60 seconds or so.
-
pnpm version
8.7.6
or higher -
node version
18.18.0
or higher -
(Optional) volta version
1.1.1
or higher
Volta is a great tool to manage different versions of package managers and Node. I use it as an alternative for nvm which I found unreliable in the past. To use Volta with pnpm
, please see these docs.
- An AWS account and the ability to get the role/user credentials.
- Ensure that your shell has the necessary AWS-related environment variables set. These are
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY_ID
andAWS_SESSION_TOKEN
. To check which variables are set, please type the following:
printenv | grep "AWS_"
For local development & deployment, I was using a role with AdministratorAccess policy.
- Install dependencies
pnpm i
- Bootstrap the backend
pnpm run bootstrap
Note: The deploy
and deploy:without-hosting
command use a non-default qualifier. This is done to prevent any collisions with existing resources. This makes the bootstrap
a required step, even if there already exists a "default" CDK boostrap in a given AWS account.
- Deploy the backend
pnpm run deploy
Note: If the role/user used to deploy the backend cannot create publicly-accessible buckets, the deployment might fail. To deploy the backend without a "website hosting" bucket, type the following:
pnpm run deploy:without-hosting
- (Optional) Upload the frontend artifacts to the "website hosting" bucket. The game is a multiplayer game, so it might be worth trying it out with others!
pnpm run deploy:website
The output of this command will contain the link to the website.
Note: This step will fail if you deployed the backend without hosting.
Note: It can take up to a minute for the game state to populate right after deployment.
-
Deploy the backend, either with or without the hosting
-
Run the application locally
pnpm run dev
- Access the application at http://localhost:5173/
Note: It can take up to a minute for the game state to populate right after deployment.
-
Deploy the backend, either with or without the hosting
-
Run the tests
pnpm run test
- Run the e2e tests
pnpm run test:e2e
The backend architecture could be split in several components.
This component is responsible for marking the user as "CONNECTED" or "DISCONNECTED". "DISCONNECTED" users are not shown in the player list.
The client subscribes to the IoT Core MQTT topic called game
. From there, AWS Lambda Functions listen to the IoT Core lifecycle events and update the user state accordingly.
IoT Core requires signed requests. Cognito was added to handle all things related to user credentials and authorization.
To update the value of the BTC in USD every 60 seconds or so, I'm utilizing an EventBridge rule that runs on a schedule. This rule invokes an AWS Lambda which fetches the latest BTC price and populates the application state.
When the "game ticker" runs, it creates a "game result" item in DynamoDB. The role of the score distributor component is to listen to "game result" item creations and update the user scores accordingly. The scores for each user are persisted in the DynamoDB.
The role of the notifier component is to stream domain events (UserPresenceEvent
, GameEvent
and PredictionEvent
) to the frontend application. The frontend application has a live connection with the IoT Core MQTT topic.
This component utilizes EventBridge Pipes. The Normalizer (transformation/enrichment step) transforms the stream data from DynamoDB into domain events. The Notifier pushes the domain events into the MQTT topic.
This component exposes a REST interface to the frontend application.
The GET /game
returns the information about the game, as well as the connected players and the predictions they have made regarding the next BTC price.
Here is how the response shape looks like:
{
game: {
id: string;
value: number;
room: string;
createdAtMs: number;
},
users: {
id: string;
status: "CONNECTED" | "DISCONNECTED";
name: string;
score: number;
prediction: "UP" | "DOWN" | null;
}[]
}
The POST /{gameId}/predict
is responsible for saving the users predictions regarding the next BTC price.
Here is how the payload looks like:
{
prediction: "UP" | "DOWN",
userId: string
}
And here is how the response looks like:
{}
-
Surface the concept of "game rooms" to the client. The concept of a "game room" is only used on the backend for the purpose of making the testing easier. It enables the creation of isolated data entities which do not influence the running application.
-
Introduce a new index to expose leaderboards. The DynamoDB already contains all the users and their scores.