Using Quarkus to develop a multiplayer game – Chapter #1 – Initial setup

October 24, 2022

Quarkus is a fantastic Java runtime environment and framework. Read this article to learn how easy it is to use Quarkus to create the server part of a multi player game (with a JavaScript client, running in a browser). Learn how to easily integrate Apache Kafka, WebSockets, a database, Qute templating for reporting etc. This is part ONE of a multi part article series.

Introduction

After finishing my book about Getting GitOps, which you can download for free from Red Hat Developers web site, I thought it’s time to have a better and more realistic use case than just a simple and boring PersonService which does nothing, except reading and writing data to a database.

Boring.

Also, if you have a look at Quarkus today, you’ll immediately realize that working with this framework is both: awesome and complex.

So why not doing both now? Creating a more realistic example for the content written in the book and explaining more about the in my humble opinion, as someone who has used Java since 1998, greatest Java framework & Java runtime available nowadays?

So I was thinking hard what to do now.

Then I played a bit with JavaScript and the HTML 5 Canvas API and I thought: Wow! The last time I was coding something which ended up in being used for a commercial game is already 25 years ago. I still was a student and I still had my good old Amiga 1200, which I have used to create a game engine. And I still remember how hard it was to bring animated graphics on the screen.

And now it’s possible with a scripting language on a Browser? — Let’s try it out!

So I started writing a game in JavaScript, which uses a backend system written in Java with Quarkus. All the GitOps concepts as discussed in my book should be reused for this project.

So this is the story about creating the game, using Quarkus in a more complex scenario than a simple HelloWorld example and about developing in a modern environment, where people are using different tools and programming languages but will deploy everything on a Kubernetes distribution. And here is the complete source code. And here you’re able to play the game as it is right now.

The general directory structure of the sources

Once you’ve forked and cloned the repository from GitHub, you can see the following folders:

  • /docs Just a bunch of images for the documentation
  • /gitops Everything you need to setup a dev pipeline in Kubernetes / OpenShift (ArgoCD apps, Tekton pipelines etc.)
  • /kubernetes-config A set of kustomized files to quickly install the complete application in a Kubernetes namespace called grumpycat
  • /melonjs-client The JavaScript client source code
  • /quarkus-server The sources of the Quarkus based server
  • /tiled All sources in Tiled MapEditor format to change levels or create your own ones
  • docker-compose.yaml A docker-compose script to start everything locally in a Docker or Podman environment
  • pom.xml The grumpy cat application is a multi module maven project. This is the main POM.

The general high-level architecture of the game

The whole game right now has four components (as seen in figure #1):

  • cat-client The JavaScript client running entirely on a browser
  • cat-server The grumpy cat server instance
  • grumpy-kafka The Apache Kafka messaging instance
  • cat-db A PostgreSQL server instance used to store scores, player actions, games and multiplayer sessions
Figure #1: Architecture of GrumpyCat
Figure #1: Architecture of GrumpyCat

Typically, the user is just interacting with the game via the client, which runs entirely on the browser (Chrome 100+, Firefox, Safari). All the processing and calculation of enemy movement is done in JavaScript. However, once the next position was calculated, it will be send to the server to either be stored in the database (single player) or being sent to other players of a multiplayer session).

The server has a rich REST API, which is being used to do several important things during the game loop:

  • Downloading a map with tile set informations
  • Creating a single player game
  • Creating and managing a multiplayer game
  • etc.

The Quarkus server

Introduction

At the beginning the Quarkus based server was just a bunch of REST services to be used to

After a while, I wanted to store player and enemy movements to have some kind of a game replay feature in single player mode. So I started playing with WebSockets and an Apacha Kafka based messaging system.

The reason for choosing Kafka is quite easy. Even if the client just sends each keystroke to the server, it could end up in getting 60 keystroke events per second, as the client runs with 60FPS. And then there are still enemy movements which need to be send to the server and stored in a database as well. The more enemies there are in a map, the more data needs to be send to the server. So there must be some kind of messaging system to have a buffer between client sending mass data and the server processing those streams and storing the data into a database.

Fortunately, Quarkus makes it very easy to use Kafka.

After I have implemented the „Replay“ feature, I wanted to have special maps where up to 4 players are able to play against each other. So there was the requirement of a multi player handling feature to be implemented as well. I played with just another REST service, but ended up in horrible stability issues, so I needed to use a different feature: A web socket.

Using a web socket makes it easy to send and consume messages to and from the client. Again, thanks to Quarkus, it’s an easy thing to implement a Web Socket.

Finally, I wanted to have some nice reports to see who has played in which game and who is the winner of a multi player match. This feature was implemented using Quarkus Qute, an easy to use templating engine.

The initial setup

As discussed in chapter one of my book or in a different blog post here, setting up a Quarkus project with everything we need, is quite easy. Thanks to Quarkus with its great extension mechanism, we just add what we need when we need it. So we’re now focusing on the basics:

  • A REST endpoint server
  • An accessible database with Hibernate ORM
  • Possibility to create a container image which should be stored in an external registry like quay.io
  • Basic Kubernetes manifest generation

This means, we need to setup a new Quarkus project with the following extensions:

  • quarkus-resteasy-reactive The default REST environment of Quarkus since 2.10ish
  • quarkus-resteasy-reactive-jackson As we are in a mixed environment, we need to be able to read and write JSON
  • quarkus-container-image-jib Building container images with JIB
  • quarkus-kubernetes Being able to generate Kubernetes manifest files
  • quarkus-hibernate-orm-panache The simplified Hibernate ORM stack
  • quarkus-jdbc-postgresql The JDBC driver to use a PostgreSQL database

The following command will create such a project for us:

$ quarkus create app org.wanja.grumpycat:cat-server:0.0.1 \
	-x resteasy-reactive-jackson,\
		container-image-jib,\
		kubernetes,\
		hibernate-orm-panache,\
		jdbc-postgresql

Looking for the newly published extensions in registry.quarkus.io
-----------
selected extensions:
- io.quarkus:quarkus-hibernate-orm-panache
- io.quarkus:quarkus-kubernetes
- io.quarkus:quarkus-container-image-jib
- io.quarkus:quarkus-jdbc-postgresql
- io.quarkus:quarkus-resteasy-reactive-jackson


applying codestarts...
????  java
????  maven
????  quarkus
????  config-properties
????  dockerfiles
????  maven-wrapper
????  resteasy-reactive-codestart

-----------
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /Users/wanja/Devel/grumpy/cat-server
-----------
Navigate into this directory and get started: quarkus dev
Code language: JavaScript (javascript)

Before we are now going to edit a source file, we need to configure the extensions, so that we are able to easily build and deploy our app later on.

a) Generating a container image

By just adding the extension, we are able to build and push an image to an external registry during the build process. In order to let Quarkus know where we want to push our image to, we need to provide some properties.

#
# container image properties
#
quarkus.container-image.image=quay.io/wpernath/cat-server:v${quarkus.application.version}
quarkus.container-image.builder=jib
quarkus.container-image.build=false
quarkus.container-image.push=false
quarkus.native.container-build=true

Code language: PHP (php)

This tells Quarkus to push the generated image to quay.io/wpernath/cat-server and to use container build if we want to go native.

We are now able to build our project by executing the following command:

$ mvn clean package -Dquarkus.container-image.push=true
Code language: JavaScript (javascript)

This will create the container image and will push it to the external registry. By using ${quarkus.application.version} we are going to get a tagged image. The version will be read from the pom.xml file.

b) Creating Kubernetes manifest files

Again, we are just adding the extension and Quarkus will generate the standard Kubernetes manifest files, like Deployment and Service. If we need other files, like a Route, we can provide it in a file called src/main/kubernetes/kubernetes.yml. If we need more control about the generation process, we can provide some properties.

Figure #2: kubernetes.yml to generate a Route
Figure #2: kubernetes.yml to generate a Route

For example, if we need to read data from a ConfigMap or a secret and project their values as properties or environment variables, we simply provide a quarkus.kubernetes.env.secrets=<secret-name> property.

#
# Kubernetes
# 
quarkus.kubernetes.part-of=grumpycat
quarkus.kubernetes.name=cat-server
quarkus.kubernetes.version=v${quarkus.application.version}
quarkus.kubernetes.env.secrets=cat-pguser-cat

# Resource management
quarkus.kubernetes.resources.requests.memory=128Mi
quarkus.kubernetes.resources.requests.cpu=250m
quarkus.kubernetes.resources.limits.memory=512Mi
quarkus.kubernetes.resources.limits.cpu=1000m
Code language: PHP (php)

If we are now building our cat-server application, we are also getting a target/kubernetes/kubernetes.yml file, which we can use to deploy our server to Kubernetes and OpenShift.

Figure #3: Generated kubernetes.yml
Figure #3: Generated kubernetes.yml

And if we’d add two more extensions to our pom.xml, we will also automatically get metrics and health checks for all of our external dependencies. Those extensions are:

$ quarkus ext add smallrye-health micrometer-registry-prometheus
????  Extension io.quarkus:quarkus-smallrye-health was already installed
????  Extension io.quarkus:quarkus-micrometer-registry-prometheus was already installed

$ mvn clean package
$ cat target/kubernetes/kubernetes.yml
...
  spec:
      containers:
        - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          envFrom:
            - secretRef:
                name: cat-pguser-cat
          image: quay.io/wpernath/cat-server:v0.0.1
          imagePullPolicy: Always
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /q/health/live
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 0
            periodSeconds: 30
            successThreshold: 1
            timeoutSeconds: 10
          name: cat-server
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /q/health/ready
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 0
            periodSeconds: 30
            successThreshold: 1
            timeoutSeconds: 10
...
Code language: JavaScript (javascript)

c) Adding and using a database

By simply adding the JDBC driver as a Quarkus extension and Hibernate as ORM mapper, we already have everything we need to start developing and using the database. As the whole process was already explained deeply in the book and here, I do not explain it again in this blog post.

The most important part for now is that we have to define some properties to make sure, we are able to use it later on Kubernetes as well. Those properties are:

#
# Datasource options
#
quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.log.format-sql=true

# the following props only for production
%prod.quarkus.hibernate-orm.database.generation=update
%prod.quarkus.datasource.username=${user:cat}
%prod.quarkus.datasource.password=${password:grumpy}
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://${host:catserver}/${dbname:catdb}

Code language: PHP (php)

They tell Quarkus to use PostgreSQL as datasource and as soon as we are not running in Quarkus’ Dev mode, the database should be reached via the mentioned URL. The variables user, passworld, host and dbname will be replaced by the content of the secret cat-pguser-cat, which we have defined in the last step. Now it’s time to see how we are deploying the database in Kubernetes / OpenShift.

For this we are going to use a Kubernetes Operator called CrunchyData PostgreSQL Operator, which needs to be installed in the Kubernetes Cluster. Then we are able to create and deploy a YAML file, which describes our database:

apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
  name: cat
spec:
  postgresVersion: 13
  instances:
    - name: instance1
      dataVolumeClaimSpec:
        accessModes:
        - "ReadWriteOnce"
        resources:
          requests:
            storage: 1Gi
  backups:
    pgbackrest:
      repos:
      - name: repo1
        volume:
          volumeClaimSpec:
            accessModes:
            - "ReadWriteOnce"
            resources:
              requests:
                storage: 1Gi
Code language: JavaScript (javascript)

If we are now adding this file to src/main/kubernetes/kubernetes.yml, we have everything we need to deploy our application including a database in Kubernetes. The steps are:

$ mvn clean package -Dquarkus.container-image.push=true
[...]
$ oc login <log into openshift or kubernetes>
$ oc apply -f target/kubernetes/kubernetes.yml
Code language: HTML, XML (xml)

This is, BTW, exactly the same for all external dependencies we might have over the development of our application. In development mode, we use a proper Quarkus extension. We configure that extension and use the %prod profile in application.properties to make sure we do not overwrite our Quarkus Dev Service instance. Then we are going to find a proper way to install that dependency in Kubernetes, either by looking for an Operator or by finding a nice Helm Chart. Then we create a custom resource YAML file and add that to our src/main/kubernetes/kubernetes.yml. And if the Operator for the dependency requires us to read a secret, we add that secret to quarkus.kubernetes.env.secrets property.

This is one of the reasons, I like Quarkus so much. It makes boring tasks so easy.

The JavaScript client

Introduction

Learning a complete new language is not always the easiest thing you could do. And learning JavaScript (a typeless language) is one of the hardest things you can do, if you’re coming from a language like Java, which has strong types. But I have to say, the last time I was using JavaScript is already quite some time ago. I have used JavaScript for some AJAX features in 2006ish. So perhaps I am not in a position to judge the language.

After I have played with HTML5/Canvas API directly, I decided to use a nice and fast game framework based on JavaScript ES6, which brings quite some nice features like layered rendering, resource loading etc. The framework is called MelonJS. There is an easy to use boilerplate repository on GitHub, which you can use in order to setup your first project with MelonJS.

Although the client runs entirely on the browser, we still need some kind of a server which lets the user download the HTML pages and JavaScript stuff of the game. I have played a lot with node and nginx as server, but that would mean I have to manually create a container image out of everything. Also, there might be a time later, where I’d need some server side Java, so I decided to use Quarkus as framework as well, although I am not using a lot of features, I am able to generate container images out of the box. And I am able to use some Java features If required.

The initial setup

First of all, let’s create the initial Quarkus project, by executing the following command, as already explained above:

$ quarkus create app org.wanja.grumpycat:cat-client:0.0.1 \
	--no-code \
	-x resteasy-reactive-jackson,\
		container-image-jib,\
		kubernetes

Looking for the newly published extensions in registry.quarkus.io
-----------
selected extensions:
- io.quarkus:quarkus-kubernetes
- io.quarkus:quarkus-container-image-jib
- io.quarkus:quarkus-resteasy-reactive-jackson

applying codestarts...
????  java
????  maven
????  quarkus
????  config-properties
????  dockerfiles
????  maven-wrapper

-----------
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /Users/wanja/Devel/grumpy/cat-client
-----------
Navigate into this directory and get started: quarkus dev
Code language: JavaScript (javascript)

As the main reason to use Quarkus here is to be able to reuse the ability to quickly generate Kubernetes manifests and a container image, we simply add the corresponding extensions and configurations to our project as we’ve done it right before with the cat-server.

Then we need to setup the JavaScript development environment by copying all the code of the melonJS boilerplate repository to corresponding folders of the Quarkus project setup.

As we are using JavaScript and the target is a browser client, we have to make sure that all our classes we’re going to create for the client will be bundled in one large JavaScript module. WebPack is the default solution for the boilerplate collection.

Once we’re done, we need to find a way to build our Quarkus project including the JavaScript stuff without loosing the ability to quickly develop with JavaScript. So I don’t want to loose the ability to run my development environment with a simple

$ npm install
$ npm run dev

But I surely want to use maven for automating the complete build. So a

$ mvn clean package -Dquarkus.container-image.push=true
Code language: JavaScript (javascript)

Should build everything and should create a working container image for my client application.

For this we are going to use the frontend maven plugin, which needs to be configured in the pom.xml of the client.

a) Configure image generation and Kubernetes manifest creation

As you’ve already seen in the chapter above, you should configure both extensions we are going to use in the client code: Kubernetes and Container-Image. Just open the application.properties file and copy the following text into it:

#
# container image properties
#
quarkus.container-image.image=quay.io/wpernath/cat-client:v${quarkus.application.version}
quarkus.container-image.builder=jib
quarkus.container-image.build=false
quarkus.container-image.push=false
quarkus.native.container-build=true


#
# Kubernetes
# 
quarkus.kubernetes.part-of=grumpycat
quarkus.kubernetes.name=cat-client
quarkus.kubernetes.version=v${quarkus.application.version}

# Resource management
quarkus.kubernetes.resources.requests.memory=128Mi
quarkus.kubernetes.resources.requests.cpu=250m
quarkus.kubernetes.resources.limits.memory=512Mi
quarkus.kubernetes.resources.limits.cpu=1000m

Code language: PHP (php)

And if you’d add the following property to your application.properties, you’d be able to start both projects (client and server) parallel:

# make sure, quarkus runs on 8081
%dev.quarkus.http.port=8081
Code language: PHP (php)

This lets the quarkus HTTP server run on a different port than the default 8080.

b) Mavenize the JavaScript boilerplate setup

There is not too much to do. Just put the following files into the root directory of your newly created maven project:

  • babel.config.json
  • package.json
  • webpack.config.js

And then copy the content of the /src folder into a newly created folder under /src/main/client of the maven project.

c) Configuring frontend-maven-plugin

As described in the readme of the plugin, configuring it is quite easy. Just put the following as a plugin section in the pom.xml file of the client and we do have what we need.

      <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>1.12.1</version>   
        
        <configuration>
          <installDirectory>target</installDirectory>
        </configuration>

        <executions>
          <execution>
            <id>install node and npm</id>
            <goals>
              <goal>install-node-and-npm</goal>
            </goals>
            <configuration>
              <nodeVersion>v16.18.0</nodeVersion>              
            </configuration>
          </execution>

          <execution>
            <id>npm install</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <phase>generate-resources</phase>
            <configuration>
              <arguments>install --unsafe-perm</arguments>
            </configuration>
          </execution>

          <execution>
            <id>npm run build</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <phase>compile</phase>
            <configuration>
              <arguments>run build</arguments>
            </configuration>
          </execution>
        </executions>    
      </plugin>
Code language: HTML, XML (xml)

Now whenever we rebuild our client application, we are getting a fresh container image with the front-end code compiled and included as well. But there is still one thing left to do

d) Configuring WebPack to build into maven target

For this we have to open the file webpack.config.js and need to make sure that the output of the module.exports section points to the corresponding target folder of our maven build.

[...]
module.exports = {
	entry: ["./src/main/client/index.js"],
	target: "web",
	output: {		
		path: __dirname + "/target/classes/META-INF/resources/generated/",
		filename: "grumpycat.bundle.js",
	},

[...]
Code language: JavaScript (javascript)

Now you should be able to develop as a frontend developer by executing

$ npm install
$ npm run dev

And once you’re finished editing JavaScript in /src/main/client/ you should be able to execute

$ mvn clean package -Dquarkus.container-image.push=true
Code language: JavaScript (javascript)

To generate a container image and all required Kubernetes files. Then you’re able to quickly install that image on OpenShift / Kubernetes, by executing

$ oc apply -f target/kubernetes/kubernetes.yml

A Quarkus multi module project

Now we have two Quarkus projects ready to be developed on. But of course it would be great, if we could have a multi module maven project. So let’s create one now.

First of all, we have to setup a main pom.xml where we are going to put all Quarkus extensions, which we are going to use in both projects. These are the Kubernetes extension and the container-image extension.

Then we have to make sure, both projects are referring to this main POM. And finally, we are going to remove those extensions, we have defined in the main POM.

Setting up the main POM

Simply create a new file called pom.xml and place it in the folder where cat-client and cat-server modules are located. Open it in your preferred editor and copy the following content in:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.wanja.grumpycat</groupId>
  <artifactId>grumpycat</artifactId>
  <version>0.0.1</version>
  <packaging>pom</packaging>
  <modules>
    <module>cat-server</module>
    <module>cat-client</module>
  </modules>
</project>
Code language: HTML, XML (xml)

Now we are going to add all common Quarkus dependencies in here:

    <properties>        
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <failsafe.useModulePath>false</failsafe.useModulePath>
        <maven.compiler.release>11</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>2.13.3.Final</quarkus.platform.version>
        <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive</artifactId>
        </dependency>
		[...]
	</dependencies>
Code language: HTML, XML (xml)

And finally we’re going to open the POMs of the dependent modules and are going to remove those dependencies which we have added to the umbrella POM and adding a parent section to their XML.

Changing the module POMs

The cat-server and cat-client POMs need to be fixed now. So open them and add the following section to it:

<artifactId>cat-client</artifactId>
  
<parent>
  <groupId>org.wanja.grumpycat</groupId>
  <artifactId>grumpycat</artifactId>
  <version>0.0.1</version>
  <relativePath>../</relativePath>
</parent>
Code language: HTML, XML (xml)

And then we can safely delete the properties section in the modules and remove those dependencies, we have added to the umbrella module. The result is a clean and easier to maintain dependency section of all client modules.

Now we are able to easily execute Maven in the location of the umbrella POM and will get what we need by just executing one command.

$ mvn clean package -Dquarkus.container-image.push=true 
Code language: JavaScript (javascript)

Conclusion

In article you’ve learned how easy it is to setup a new Quarkus project which should also generate Kubernetes manifest files and should produce a container image.

We now have everything we need to start developing our game, which we are going to discuss in the next chapters of this article series.

Next time we are developing the basic client to download a map file (and display its contents to the screen with MelonJS techniques) and we are going to implement a Highscore REST API to store the scores.

We are also going to use Kafka to pickup player movements and store them in a database.

Then we are going to setup multi player management for our game.

I hope, you enjoyed reading this article.