A house of cards

A house of cards

Episode 02

IT Bati's photo
IT Bati
·May 2, 2022·

7 min read

Honk!

I know you missed me, but I was swamped. No, I didn’t eat all the time! I slept a lot and strutted around. This is very important for the goose!

Argh, whatever. Bullshit aside, let’s get back to the Game of Lawns.

Where are my notes? I’m pretty sure I left them right here, on the table. Or on the chair. Honk, where they are...

*half an hour later*

Honky honk that honky goat! This... this... creature ate the notes I made! I bet it did it on purpose! Envious honk! Oh my, I don’t want to start over.

Ok, ok. I will restore them later. I hope I remember most of the data. And next time, I need to make copies and store them in different places! This goat can try to repeat its crime. Honk, sounds like this is a wonderful place for some cloud storage advertisement.

And now I want to think about software architecture. If I'm not confused target technology stack is ReactJS for the frontend, NodeJS for the backend, and MongoDB for storage.

First of all, let’s consider high-level architecture and cross-component communication.

ReactJS → NodeJS: API Calls

ReactJS ← NodeJS: Server Events

NodeJS ←→ MongoDB

So, what options do we have for front-back communication?

Ok, let’s be honest. SOAP and SSE are not the options at all. GraphQL is great when you have many objects and a complex data model. But we don’t. So it will be absolute overkill.

But since this is a multiplayer online game, we need interactions, and pulling data is also too old-style. So, it looks like we need REST + WebSocket.

Database communication now. I see two options - native MongoDB driver for NodeJS and Mongoose - object modeling framework for MongoDB. On one side, Mongoose is a little bit overkill for such a small project. On the other side, we will still need to validate in/out data and do this manually, a kind of bicycle invention.

In addition, we need to think about receiving events from Mongo to build a real pub/sub. Mongoose can do this out-of-box. Maybe native driver also can, but who cares.

Let me draw all of this…

communication_diagram.png

Honk, done. Hid three instances of this note in different locations. Moving further. We have a high-level picture. Now let’s go deeper and describe some of the details. I suggest going that path: Data model → Data Flow → API Specification.

Oh, honk... To work on the Data model, I need to restore the Class diagram I made last time. Okay, let me think and remember...

Class_Diagram.png

Ok, looks like this is it. But this is a Normal Form of data, applicable for relational databases. And we have MongoDB, which is a document-oriented database, and we maybe can find a more suitable data format. Let’s honk a little.

At the center of our process is a Game. The game has two players. Each player has two boards. Each board has cells. A cell can be healthy or hit, empty or with a lawn. A lawn is related to a lawn piece.

Honk. It looks almost like a single nested structure, excluding lawn pieces. Can we simplify this part somehow to have directed acyclic graph? If we find the solution, we will have much simpler data that ideally fit into the document-oriented form.

What is the board? The board is the matrix, where each value represents some state. And what states we can have?

  • empty
  • empty-hit
  • full with pieces 1 to 4
  • full-hit with pieces 1 to 4

Hm. What if we will use null value for empty state, zero for empty-hit, positive numbers from 1 to 4 range for full and negative numbers from -1 to -4 for full-hit? Then we can represent the board as an array of arrays. Or, even as a single array of one hundred elements. But it will become harder to operate. MongoDB is ok about nested arrays, so no need to over-engineer here, honk!

Let’s try to describe our structure using OpenAPI 3.0 Specification. In my opinion, this is the best way to follow API First principle. We will use the Yaml format since it’s easier to read.

openapi: 3.0.3
info:
  title: Game Of Lawns
  description: This is a full specification for API and data components for the "Game of Lawns" Game
  version: 1.0.0

components:
  schemas:
    Game:
      type: object
            readOnly: true
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
          description: Unique identification for each game generated by the server
        state:
          type: string
          enum: ["idle", "player1", "player2", "cancelled", "finished"]
        player1:
          $ref: '#/components/schemas/Gooser'
        player2:
          $ref: '#/components/schemas/Gooser'

    Gooser:
      type: object
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
          description: Unique identification for each player generated by the server
        name:
          type: string
          description: Visible name of a player
        yard:
          $ref: '#/components/schemas/Yard'
          description: Own yard
        hunt:
          $ref: '#/components/schemas/Yard'
          description: Yard of the opponent representation

    Yard:
      type: array
      items:
        type: array
        items:
          type: integer
          nullable: true
          minimum: -4
          maximum: 4
        minItems: 10
        maxItems: 10
        description: Array representation of the board
      minItems: 10
      maxItems: 10
            readOnly: true

Swagger UI will show our components something like this:

swagger_ui.png

Honk, not bad, not bad! Now we need to specify data flow and REST endpoints. What actions will we perform using REST API?

Gooser creation

I don’t want to make full-power registration. Gooser should be something very basic... Just unique code and name. We can store them in the browser between sessions to not save them on the server at all! To prevent changes, it can be a JWT. So the only thing we need to be sure of is that the token is valid.

To create something in REST API we need to perform a POST request for a list of entities endpoint. Something like

POST /gooser HTTP/1.1
Host: gol.it-club.ge
Accept-Encoding: gzip, deflate
Content-Type: application/json; charset=utf-8
Content-Length: 19

{"name": "IT Bati"}

In OpenAPI notation, it will look like this (as an addition to what we already have, of course):

/gooser:
    post:
      summary: Create a new gooser, which is mostly stateless, since all user info will be stored in JWT
      operationId: createGooser
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Gooser'
      responses:
        201:
          description: Gooser created, return JWT with gooser info
          content:
            application/json:
              schema:
                type: object
                properties:
                  jwt:
                    type: string
        400:
          description: 'Wrong request parameters'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

And visualization like

swagger_ui.png

Honk, well, well. Now it looks like something already! Moving forward. Since we registered a gooser, we somehow need to make a game and then share it with the second player. That means we need three operations:

Create a game, get game info, join a game

For REST API this means:

POST /game
GET /game/:id
PATCH /game/:id

Hm. Also, I think we need to make biting action as a REST endpoint since it should be a synchronous operation. In RESTful API there is no such thing as action. All we can operate with are objects. Honk, ahahaha, we will have a new object Bite! It will be publicly nested to the game and will have two-directional coordinates:

POST /game/:id/bite with {"x": <x coord>, "y": <y coord>}

Honk, I think that’s all from a REST point of view. And I’m too lazy to describe each step for OpenAPI spec one more time, so I will just write it and put it somewhere later.

*later: https://gol.it-club.ge *

Now let’s talk about WebSocket. The protocol here will be much simpler. We only need to receive events - something that happened at the server or performed by another player. The structure of events will be something like this:

{
    "event": "bite", 
    "coords": {"x": <x coord>, "y": <y coord>}
    "next": "player1"
}

Honk, I'm not sure if all of this is the really best way of implementation and if it will not fall apart like a house of cards, but I WANT TO CODE SOMETHING ALREADY, H*NKY HONK!

Where is my laptop? H*nk, it’s still at the office! Damn, I'm heading there right now! No more theory; I want to code!

Honk, don't go too far, I will get back soon!

 
Share this