DEV Community

Cover image for Startup CliX: Serverless Pusher Contest Entry written in JavaScript
K
K

Posted on • Edited on

Startup CliX: Serverless Pusher Contest Entry written in JavaScript

After a looong weekend of getting pretty much wrecked.

me getting wrecked

I finally found the time to write a proper blog post for the contest entry.

What I built

I built a multiplayer clicker game with Pusher & AWS SAM.

Gameplay

You start with seed capital of $30000.

Every round you create products by clicking as fast as you can and submitting your product.

Your products price (= your profit) is calculated by 500000 * clicks / milliseconds

You also lose $5000 every round, because of the burn-rate of your startup.

So you'll probably lose money every round, haha.

If you lose all your money you are out of the game.

Last player left, wins.

Demo Link

Link to Code

How I built it

Stack

My idea was to build it completely serverless. While Pusher allows for a serverless setup, it sadly doesn't have a serverless pricing model, hehe.

I used AWS SAM, which is an extension of CloudFormation, so besides Pusher, it is 100% infrastructure as code.

Amazon API-Gateway for HTTP requests. Joining games, submitting products every round, receiving Pushers webhook data.

AWS Lambda for all the server side calculations. Checking for empty games, calculating profits for every round, notifying players via Pusher of game events (round:start, round:end, etc).

AWS Step Function for game coordination. Starting games, starting rounds, calling Lambda every round to calculate things and notify players via Pusher.

Amazon DynamoDB to store game-data (products, player count) to make it accessible for the state-machine controlled Lambda functions.

Pusher Channels to get data from the back-end to the clients without any need of polling.

The front-end is built with create-react-app and Bootstrap (v4 + Bootswatch Sketchy).

Architecture

I also made an architecture diagram:

Game Architecture

As you can see, the data comes in from players and Pusher via HTTP to my serverless back-end and is fed back to the clients via Pusher Channels.

The Role of Pusher Channels

Pusher Channels allows to broad/multicast events to the clients without the need of a direct connection to the clients.

You just call the Pusher HTTP-API to send an event and Pusher takes care to distribute it to the clients.

This is pretty cool, because WebSockets would force you to keep an open connection to all clients, which isn't possible with Lambda functions. They can only run for about 5minutes.

When a Lambda function is called (via API-Gateway or Step Functions) it can simply do its thing, send a HTTP-request to Pusher and get suspended again, while Pusher keeps track of open connections.

The Pusher API also allows to get the state of all the channels via HTTP, so you can start a Lambda, check who is online and send data depending on channel state if you like.

Problems

Slow Realtime

Pusher is advertising its Channels as realtime, but this isn't really the case.

It uses Web technology at its core, so it's build completely on TCP and it adds at least one other server (= network hop) to the whole architecture.

First you have the WebSockets connections from Pusher to the clients, which have less latency than a full HTTP request for every event, but still bring a few round-trips with them.

And second, you use the HTTP API on server side to send events to Pusher, which leads to client -HTTP-> back-end -HTTP-> Pusher -WebSockets-> client in terms of latency.

Its unique selling point is more of pushing data to clients with a very simple setup (hence the name Pusher, haha), but not minimal latency.

It lets you use client-events to cut out your back-end as the middle man to reduce the latency even more, but you can't run code on the Pusher side of things for every event, which reduces the usefulness rather much.

So at the moment we are talking about less than 10 events per second. Which is more than enough for most applications.

This is why I don't send every click to the server, but gather them every round. This allows for one HTTP request every 10 seconds (10 second round)

Something like Lambda for Pusher events, running on Pushers infrastructure would be a killer feature, hehe.

Unidirectional Step Functions

The next problem was AWS step functions. I found it rather nice to model the game state and the rounds etc. but I haven't found a way to get data into the state-machines easily.

The problem is as follows:

You define a state-machine. This state-machine can be executed multiple times.

Every state of the state-machine can either call a Lambda function with some input or create a activity task.

I had the idea to define the game as a state-machine and every execution of a state-machine would be a running game.

While every execution can wait for an activity to be completed by a worker (for example an API-Gateway Lambda) the worker can't filter the tasks in every activity for execution.

So I wasn't able to make workers execution/game specific.

I had to add DynamoDB to the mix to get data into the state-machine.

Player sends a finished product via HTTP (API-Gateway -> Lambda) and the back-end would store it into DynamoDB, the gameId being the primary key.

When the state-machine decides a round is finished, for example after a Wait-state of 10 seconds, it starts a Lambda function that looks into DynamoDB, calculates the results and publishes them to the clients.

DynamoDB has nice concurrency features, so it wasn't too bad and allowed to synchronize players more easily in the end.

Additional Resources/Info

I also wrote a bunch of blog-posts for the process.

Conclusion

It was fun to use my new back-end skills and see myself doing full-stack stuff for real now.

I learned much about Pusher, AWS and serverless in general.

I would appreciate all your likes, unicorns and what not, but even if I don't win it's nice to add this project to my portfolio (maybe after a cleanup, lol)

Also, as always, issues and pull-requests are welcome. Maybe someone has ideas that would make the game really fun to play, hehe.



I appreciate all your likes and unicorns!

Top comments (6)

Collapse
 
avalander profile image
Avalander • Edited

Awesome, man, and congratulations on finishing your entry! The game was fun to try, and great posts of the process too!

Here's a smiling unicorn for you!

Minuette

Collapse
 
kayis profile image
K

Thanks!

Yes, I feared I won't finish it, especially as I saw your submission.

I had many other things planned. I even wanted to polish it up, but didn't find the time...

Collapse
 
avalander profile image
Avalander

Yeah, we're on the same boat. I had so many ideas, but life got in the way and I couldn't put in as much time as I wanted in the end. Still, you did great, and I loved your posts with thorough explanations of your process!

Thread Thread
 
kayis profile image
K

I liked yours too. Found it very motivating to see others planning games and following through with it :D

Collapse
 
goodidea profile image
Joseph Thomas

I'm excited to read through all of your posts and poke through your code. Thanks for taking the time to write all of this up!!

Collapse
 
kayis profile image
K

Hope the you take anything away from it :)