Skip to content
This repository has been archived by the owner on Jan 14, 2024. It is now read-only.

docs: required documentation #73

Merged
merged 44 commits into from
May 16, 2021
Merged
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7009802
docs: split gameplay section
arekisanda May 15, 2021
b7c8357
docs: rename running for production section to operation
arekisanda May 15, 2021
3058896
docs: add infrastructure section with todos
arekisanda May 15, 2021
024d549
docs: add design decisions section with todos
arekisanda May 15, 2021
9fd6953
docs: rename technical implementation section to architecture
arekisanda May 15, 2021
fbcd0e1
docs: add big picture sub-section
arekisanda May 15, 2021
283ee2a
docs: rename service names sub-section to separation of concerns
arekisanda May 15, 2021
2de7eb3
docs: rename service name column to role / move it / add description …
arekisanda May 15, 2021
a667667
docs: convert frontend sub-section to sub-sub-section / use one line …
arekisanda May 15, 2021
241774a
docs: convert reverse proxy sub-section to sub-sub-section / rename i…
arekisanda May 15, 2021
4bb4c12
docs: convert backend sub-section to sub-sub-section
arekisanda May 15, 2021
06c9ac6
docs: convert database sub-section to sub-sub-section / rename it to …
arekisanda May 15, 2021
41a5526
docs: use one line per sentence in communication sub-section / add todos
arekisanda May 15, 2021
7cb0105
docs: add configuration sub-section with todos
arekisanda May 15, 2021
7ea1ce3
docs: add maintainability sub-section with todos
arekisanda May 15, 2021
5a8626b
docs: move architecture section
arekisanda May 15, 2021
40c15a5
docs: add bi-directionality sub-sub-section
arekisanda May 15, 2021
a76484a
docs: finalize authentication sub-sub-section / finalize symmetric JW…
arekisanda May 15, 2021
1143f1c
docs: add control flow sub-sub-section
arekisanda May 16, 2021
b67f67f
docs: add asynchronicity sub-sub-section
arekisanda May 16, 2021
df4e017
docs: add jwt sub-sub-section
arekisanda May 16, 2021
dd346e7
docs: update configuration sub-section as suggested
arekisanda May 16, 2021
de35a96
docs: update docker setup sub-section as suggested
arekisanda May 16, 2021
0e9e80b
docs: update ci/cd sub-section as suggested
arekisanda May 16, 2021
635f6f0
docs: remove pki sub-section / move certbot todo to hosting sub-section
arekisanda May 16, 2021
0d2d528
docs: update hosting sub-section as suggested
arekisanda May 16, 2021
eabe749
docs: update grpc sub-section as suggested
arekisanda May 16, 2021
810c961
docs: update micronaut sub-section as suggested
arekisanda May 16, 2021
cf1a00b
docs: update kotlin sub-section as suggested
arekisanda May 16, 2021
7dbe0c8
docs: update notification sub-section
arekisanda May 16, 2021
146aef5
docs: improve load balancing sub-sub-section
arekisanda May 16, 2021
e73b616
docs: update envoy sub-section as suggested
arekisanda May 16, 2021
d043b06
docs: update reverse proxy sub-section
arekisanda May 16, 2021
326c2a7
docs: update maintainability sub-section as suggested
arekisanda May 16, 2021
2cf477b
docs: update svelte sub-section
arekisanda May 16, 2021
a30a2e1
docs: remove obsolete rule
arekisanda May 16, 2021
cc551a3
docs: replace fraction with faction
arekisanda May 16, 2021
d854cad
docs: update authentication sub-sub-section as suggested
arekisanda May 16, 2021
c522432
docs: update maintainability sub-section as suggested
arekisanda May 16, 2021
2b29719
docs: update reverse proxy sub-section as suggested
arekisanda May 16, 2021
c90c329
docs: replace see <link> with see <link> for details
arekisanda May 16, 2021
81a801f
docs: fix typo / use one line per sentence
arekisanda May 16, 2021
2bdf3b4
docs: add prerequisites sub-section to development section
arekisanda May 16, 2021
2cb8154
docs: add getting started sub-section to development section
arekisanda May 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 179 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,187 @@
The modern, distributed and scalable implementation of a game inspired by Connect Four.

# Gameplay
- There are two main fractions: Yellow vs Red

## Rules
- There are two main factions: Yellow vs Red
- There's only one big playing field for all players
- Each player can make a move after a certain timeout
- Other players cannot place a chip close to another chip for a certain timeout
- If more people join, the timeouts are altered, and the playing field scales automatically
- More people => make playing field wider
- More people => make playing field slightly higher
- Fewer people => Mark (now superfluous) rows and columns as soon to be deleted, delete after a certain time passed
- If a disk you placed is part of a 4 disk long line you get points, and the associated columns are cleared
- You get more points if you're on the losing fraction for balance purposes

## Future Plans
- You get more points if you're on the losing faction for balance purposes
- You can exchange your points for perks and skins
- To ensure fairness, some bots might be added to balance the teams

# Running for Production
The easiest way to get ChaosConnect running is using docker-compose. We do not provider support for running the software components otherwise.
# Architecture

## Big Picture
![Architecture Overview](https://www.plantuml.com/plantuml/png/jPTFRvim5C3l_XHUrMlI0ELFfIegtKuxT5FNsxQ31oxnOcngJDgkwdUVyy2a9K6g5FCEMFRxy-Cz7WPVrPeetPGSVM8YuqrEyIMNXQpFSfcjgPfNHhVSK_wjfHXHhQNcR4nPoLeNYjOFVCIWtb2kwOnbVNnKLuffYa-fsCZdIicdP_pJM_XFGN3cHR_n2rgCYvya2qSoZeaJa6anII_-P1ZV8YAuJeE9EqROPnMvnzX4l7UPUKunbXBlP-SU4nxCyDmnve3LEO0SOw9ufC7TOBma0anug4AX6mp4YG439NAFiUraCMs91fKxTu81IZj2qBrj9d25iUFXP-vFW05vp_6EnwGN43wrc8Flq94OFqqj9assepS59jrAs67IAJ5z40ym_ZIOPD02IJG9vcA_aRsfCNO_60OE4gUODBAVbo7Q4GQU2NIR_RoOqRwObd8inkvYlcqy8x5TjvFZ6tgtYq6yBeN0LCkMy3XCQ3ZHOXSwR5E03umLpyKFLBfBA8A4ukv1xQhb0jFkQcQyC14JArrwWSj_wDIAOGv_CzrXBAXzqA4FWtiC14yN4mfwHKWppIc-ezbAI2wBP_njGzM6qKU4wZKJ5L6anAKKCbifMLAiLKFVQ79wJngkx-YJJZahH4c5nfcvK8LGHR8rAIW-kJnjZvRB4_o2C5OqqqGK73HlZd_BN-ABh2ecJp2fyRsI9ep8ZSD3GnimtXq8ZUsFYjByaHHIW3qi6-EU-bNIxL4Nb7L6E1F5jT6Pa70NW_je3x4cRESs-zSebgt0MyYSUXzmCJLXTU_XVy23Iv5BqRi4nkKLZJdToXK1MwYm8XpIWGhTizPsUqfVWad-nomBgStoDSlPDYAJWd2SoR9i1Cl8TexrIiccME7YzljdyBlnJ5lOskFPigihNCrqK4a4fS6NCwCPt1IKO5GJ7DIycmBTf85kF3nlvhk6QEU3Eu5LDHz6l3ANfkJ_0G00)

## Separation of Concerns
The service names are all a reference to the popular anime `JoJo's Bizarre Adventure`.

| Name | Role | Description |
| ---------- | --------------- | ----------------------------------------------------------------------------- |
| Doppio | Frontend | Svelte-based web client |
| Speedwagon | Load balancer | Envoy-based load balancing reverse proxy |
| Joestar | Scaling backend | Micronaut server for user authentication and caching |
| Rohan | Central backend | Micronaut server for central storage and processing of the game and its users |

### Frontend
The frontend is written in [Svelte](https://svelte.dev/).
As it's main purpose is to display the current state of the board, we decided that frameworks such as Angular are overkill.

### Load balancing

We use [Envoy](https://www.envoyproxy.io/) as a reverse proxy to handle load balancing.
We had to use Envoy as it's the only reverse proxy which currently supports `grpc-web`.

### Backend
The backend is split into two parts:
* Scaling: Communicates directly with our Frontend, issues and validates JWT, caches game state and sends game updates to all clients
* Central: Manages the actual game state, synchronizes requests and handles game logic, stores persistent information, such as user credentials and scores, in a json document

Both backends use [Micronaut](https://micronaut.io/) with [Kotlin](https://kotlinlang.org/).

### Storage
We decided not to use a database but instead store the (very minimalistic) data in a JSON document.

## Communication

### Bi-directionality

Bidirectional communication is enabled through [gRPC](https://grpc.io/).
For example, this allows `Rohan` to send a game update event to all `Joestar` instances, which then forward them realtime to all `Doppio` clients.

### Authentication

Because we use gRPC for communication, which is based on HTTP, we send the authentication token in the [Authorization Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization).
We use a custom solution based on symmetric JWT for authentication, as we did not want to commit to a vendor-specific or work-in-progress solution (see [Symmetric JWT](#symmetric-jwt) for details).

### Control flow

We use streaming to propagate game state updates from Rohan to Joestar servers and from Joestar servers to Doppio clients.
All other communication is request-based.

### Asynchronicity

The streaming API offers hooks for various events.
The request-based API is asynchronous by nature as well, but allows for convenient programming styles that are similar to those common in synchronous contexts.

| Control Flow | Kotlin | TypeScript |
| ---------------------- | ----------------------------------------------------------- | ----------------------------------------- |
| Streams | `kotlinx.coroutines.flow.Flow` + callback methods | `ClientReadableStream` + callback methods |
| 'Synchronous' Requests | `suspend fun` + `kotlinx.coroutines.BuildersKt.runBlocking` | `Promise` + `async` + `await` |

### JWT

JWTs are issued and terminated by Joestar servers.

## Configuration

We use [Micronaut Application Configuration](https://docs.micronaut.io/latest/guide/index.html#config) to make some parts of the application configurable.
These configurations can be set through the `application.yml` file at compile time or through an environment variable at runtime.
The following configurations are probably the most interesting ones to configure, for a full list see the source code:

- Joestar
- `JOESTAR_PORT`: The port at which Rohan is listening
- `ROHAN_SERVER`: The hostname or ip of the Rohan server
- `ROHAN_PORT`: The port at which Rohan is listening
- `JWT_SECRET`: The base-64 encoded 512-bit private key used for signing the JWT
- Rohan
- `ROHAN_PORT`: The port at which Rohan is listening

## Maintainability

We use state-of-the-art technology and are using the latest version to take advantage of the latest language and framework features.
For example, many Kotlin coroutine features we rely upon to easily implement real time updates were released as stable May 14th, just in time for the v1 release.

We try to avoid unnecessary code cohesion and favor testability instead.
Currently, we have more than 200 unit tests.

# Infrastructure

## Docker setup

In order to enable easy deployment every service is dockerized.
We do not use docker containers for developing, but you can easily build the containers locally to test their cross-container communication.
We also use docker to run gRPC code generation for `grpc-web` to ensure the code gets generated with the same compiler version on every device.

The docker images are generally optimized for file size and try to use the smallest available base image.

Our images also implement [Docker Healthchecks](https://docs.docker.com/engine/reference/builder/#healthcheck) which can be used to determine if a server is irrecoverably broken.

## Reverse Proxy

We use a dockerized Envoy reverse proxy server as a load balancer (see [Envoy](#envoy) for details).

## CI/CD
We use [GitHub Actions](https://github.com/features/actions) to test and build ChaosConnect automatically.
Every push is tested and built and Pull Requests can only be merged if all tests succeed.
Creating a new [Release](https://github.com/PascalHonegger/ChaosConnect/releases) automatically builds and publishes all required [Packages](https://github.com/PascalHonegger?tab=packages&repo_name=ChaosConnect) such that users can run ChaosConnect with a specific version without needing to build the images themselves.

## Hosting

At the time of writing this, the latest stable version of ChaosConnect is deployed at [chaos.honegger.dev](https://chaos.honegger.dev/).
We decided to use the cloud provider [Linode](https://www.linode.com/) because they provide fair pricing and a good free initial credit.

Setting up hosting was very easy, you just copy the docker-compose.yml file, replace the placeholder jwt-secret with a generated one and you're almost ready to start.
If you don't already have a valid certificate around, you can easily generate one using [Certbot](https://certbot.eff.org/) using a command similar to the following and mounting them to their corresponding location within the Speedwagon container:

`sudo docker run -it --rm --name certbot -v "/etc/letsencrypt:/etc/letsencrypt" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" -p 80:80 certbot/certbot certonly --standalone`

# Design Decisions

## gRPC
We wanted real time updates and gRPC provides a way to stream real time updates.
Implementing real time update from server-to-server and server-to-browser is no easy task and implementing a type safe variant through websockets would have taken a lot longer than just using a gRPC library.

## Micronaut

Micronaut offers a couple of benefits:

- It supports gRPC out-of-the-box
- It offers Configuration Management
- It provides fast startup and performance by using build-time dependency injection

## Kotlin

Kotlin is a modern, concise, and `null` safe programming language, which offer great language and ecosystem support for gRPC.
For example, implementing parallel code using Kotlin coroutines is vastly easier than using plain old java.

## Svelte

Svelte is a modern, light-weight SPA framework that meets the needs of small projects like ours.

## Envoy

Envoy is the only reverse proxy with official and native `grpc-web` support known to us.

Envoy uses round-robin for load balancing and only considers servers for load balancing which fulfill a readiness check.
For example, a Joestar instance without a valid rohan connection is not considered ready and will not be load-balanced.

## Symmetric JWT

We couldn't easily use the `micronaut-security` package because the feature is still WIP for gRPC (see [`micronaut-grpc` issue #164](https://github.com/micronaut-projects/micronaut-grpc/issues/164) for details).
The official token-based authentication works by using Google as a token provider, but we didn't want to have a vendor lock-in.

In the end, we decided to use simple symmetric tokens because of the project scope.

The client does use the metadata to send the token [which was recommended back in 2018 by the `grpc-web` team](https://github.com/grpc/grpc-web/issues/207#issuecomment-406134504).

## Notifications

In order to preserve network bandwidth, which can be pricey depending on the hosting provider, we use real-time updates with light-weight change events instead of complete states.

# Operation

The easiest way to get ChaosConnect running is using docker-compose.
We do not provide support for running the software components otherwise.

## Prerequisites
arekisanda marked this conversation as resolved.
Show resolved Hide resolved
The `docker-compose.yml` file contains a placeholder for the JWT signing key.
Expand All @@ -35,6 +201,14 @@ docker-compose up --scale joestar=5 -d
```

# Development

## Prerequisites

For local development JDK 16, Node 16 and a modern version of docker need to be installed.
Our project also contains run configurations for IntelliJ, which is our IDE of choice.

## Getting started

In order to run components natively in development mode, the following commands are good to get started:

```sh
Expand Down Expand Up @@ -70,36 +244,3 @@ docker-compose -f docker-compose.gen.yml up gen_self_signed_cert
# Run services hosted under http://localhost:5001, built locally, run 2 joestar instances
docker-compose -f docker-compose.dev.yml up --build --scale joestar=2
```

# Technical Implementation

![Architecture Overview](https://www.plantuml.com/plantuml/png/jPTFRvim5C3l_XHUrMlI0ELFfIegtKuxT5FNsxQ31oxnOcngJDgkwdUVyy2a9K6g5FCEMFRxy-Cz7WPVrPeetPGSVM8YuqrEyIMNXQpFSfcjgPfNHhVSK_wjfHXHhQNcR4nPoLeNYjOFVCIWtb2kwOnbVNnKLuffYa-fsCZdIicdP_pJM_XFGN3cHR_n2rgCYvya2qSoZeaJa6anII_-P1ZV8YAuJeE9EqROPnMvnzX4l7UPUKunbXBlP-SU4nxCyDmnve3LEO0SOw9ufC7TOBma0anug4AX6mp4YG439NAFiUraCMs91fKxTu81IZj2qBrj9d25iUFXP-vFW05vp_6EnwGN43wrc8Flq94OFqqj9assepS59jrAs67IAJ5z40ym_ZIOPD02IJG9vcA_aRsfCNO_60OE4gUODBAVbo7Q4GQU2NIR_RoOqRwObd8inkvYlcqy8x5TjvFZ6tgtYq6yBeN0LCkMy3XCQ3ZHOXSwR5E03umLpyKFLBfBA8A4ukv1xQhb0jFkQcQyC14JArrwWSj_wDIAOGv_CzrXBAXzqA4FWtiC14yN4mfwHKWppIc-ezbAI2wBP_njGzM6qKU4wZKJ5L6anAKKCbifMLAiLKFVQ79wJngkx-YJJZahH4c5nfcvK8LGHR8rAIW-kJnjZvRB4_o2C5OqqqGK73HlZd_BN-ABh2ecJp2fyRsI9ep8ZSD3GnimtXq8ZUsFYjByaHHIW3qi6-EU-bNIxL4Nb7L6E1F5jT6Pa70NW_je3x4cRESs-zSebgt0MyYSUXzmCJLXTU_XVy23Iv5BqRi4nkKLZJdToXK1MwYm8XpIWGhTizPsUqfVWad-nomBgStoDSlPDYAJWd2SoR9i1Cl8TexrIiccME7YzljdyBlnJ5lOskFPigihNCrqK4a4fS6NCwCPt1IKO5GJ7DIycmBTf85kF3nlvhk6QEU3Eu5LDHz6l3ANfkJ_0G00)

## Service Names
The service names are all a reference to the popular anime `JoJo's Bizarre Adventure`.

| Service | Name |
| ------- | ---- |
| Frontend | Doppio |
| Loadbalancer | Speedwagon |
| Scaling Backend | Joestar |
| Central Backend | Rohan |

## Frontend
The frontend is written in [Svelte](https://svelte.dev/). As it's main purpose is to display the current state of the board, we decided that frameworks such as Angular are overkill.

## Reverse Proxy
We use [Envoy](https://www.envoyproxy.io/) as a reverse proxy to handle load balancing. We had to use Envoy as it's the only reverse proxy which currently supports grpc-web.

## Backend
The backend is split into two parts:
* Scaling: Communicates directly with our Frontend, issues and validates JWT, caches game state and sends game updates to all clients
* Central: Manages the actual game state, synchronizes requests and handles game logic, stores persistent information, such as user credentials and scores, in a json document

Both backends use [Micronaut](https://micronaut.io/) with [Kotlin](https://kotlinlang.org/).

## Database
We decided not to use any database but instead store the (very minimalistic) data in a json document.

## Communication
Bidirectional communication is enabled through [gRPC](https://grpc.io/). For example, this allows `Rohan` to send a game update event to all `Joestar` instances, which then forward them realtime to all `Doppio` clients.