Skip to content

Architecture

Dennis Dubbert edited this page Mar 19, 2020 · 7 revisions

What Services and Kafka Topics are needed ?

Which of those services need to provide an API and which wont ? In other words: Which are only workers and which need to directly be addressed by a client?

In addition, how many different Events and Topics are needed for connecting the Services? What are their names, how should the corresponding events data structure be, are those events grouped or is each event send as a standalone and what Services are posting to and reading from which Topic?

Architecture_dub_io

Topics:

NodePositions:

Everything in the game is a node. Services creating such nodes need to publish them as groups to this Topic. Having a single Topic instead of one for each node type has the advantage of scalability. New game objects can be added easily and merged together inside of this Topic. There is only one service planned to read from this topic, so a single Topic will be used at first. The data structure will be as follows:

{
    value: {
        type: String, // NodeGroup-Type
        nodes: [
            {
                id: String,
                type: String, // NodeGroup-Type
                title: Sting, // Name or Title of the node
                position: {
                    x: Float,
                    y: Float,
                },
                radius: Float,
                sprite: String?, // If an image instead of color should be used to display this node (e.g. food, obstacles etc.)
                color: String?, // Background color of node if no sprite was provided
            }
        ]
    }
}

GridObjects:

After all the different nodes have been merged to a single data structure, this structure needs to be made available, which happens through this Topic. It always contains the newest state of the game objects. A Column-Row structure has been chosen, because it makes rendering only the parts around a player and collision detection much faster. Therefore a cell should also at least be the size of the biggest node, because then in collision detection, only the cell itself and the ones surrounding it need to be tested. For rendering the cell size also will be provided. To make these processes even faster, an additional list of all player nodes will be added, also containing the corresponding grid indices. This topic will be read by multiple consumers, which is why it will start with an amount of 3 partitions, which might be increased down the road.

{
    value: {
        cellSize: Float,
        rows: [
            {
                cells: [
                    {
                        nodes: [
                            {
                                id: String,
                                type: String, // NodeGroup-Type
                                title: String, // Name or Title of the node
                                position: {
                                    x: Float,
                                    y: Float,
                                },
                                radius: Float,
                                sprite: String?, // If an image instead of color should be used to display this node (e.g. food, obstacles etc.)
                                color: String?, // Background color of node if no sprite was provided
                            }
                        ]
                    }
                ]
            }
        ],
        playerNodes: [
            {
                id: String,
                title: String, // Name or Title of the node
                position: {
                    x: Float,
                    y: Float,
                },
                radius: Number,
                sprite: String?, // If an image instead of color should be used to display this node (e.g. food, obstacles etc.)
                color: String?, // Background color of node if no sprite was provided
                gridIndices: {
                    x: Int, // The column of the Grid the player is in
                    y: Int, // The row of the Grid the player is in
                },
            }
        ],
    }
}

Collisions:

If a collision was found it needs to be made public to all services. All collisions are therefore published to this Topic. Now each service can select and react to the events that are important for it. To reduce comparisons and load, only player nodes will be checked for collisions with any other node. If a collision was detected, the two colliding nodes are send in one message to the topic. This topic will be read by multiple services (4 at the moment) and therefore starts with a partition count of 4.

{
    value: {
        collisionNodes: [ {
            id: String,
            type: String, // NodeGroup-Type
            title: String, // Name or Title of the node
            position: {
                x: Float,
                y: Float,
            },
            radius: Float,
        },
        {
            id: String,
            type: String, // NodeGroup-Type
            title: String, // Name or Title of the node
            position: {
                x: Float,
                y: Float,
            },
            radius: Float,
        }],
    }
}

UserEvents:

Actions of services might have an impact on a user. This Topic allows for sending those events to the User service, so that it can integrate changes into the corresponding user data structure (e.g. grow when food got collected, reduce size or die when boundaries or viruses where hit, reset position when warps were hit or any item event). Only the User service reads from this topic, so a partition count of 1 will be sufficient at first.

{
    value: {
        userId: String,
        event: Enum ['GROW', 'GROW_UP', 'SPEED_UP', 'DESTROY', 'INVULNERABLE', 'POSITION_RESET'],
        value: Float?, // If event needs a second value parameter
        activeTime: Float?, // If the change only takes effect for a specific time
    }
}

Services:

API-Gateway:

A service that represents the single entry point to all services. It processes all user requests, asks the corresponding services and returns their responses to a client. All services that need to be reached by a client must be known by this Gateway.

Services connected to the API-Gateway

User:

Provides a way to create a Player-object, to change the direction a player is moving or to notify player deaths and therefore needs to be connected to the Gateway. It also stores each active player, their size, current position, sprites, colors and much more. Periodically it processes player movements and publishes them as a group to the NodePositions-Topic. It needs to know, when players are colliding, to remove the smaller player and add size to the bigger one. It only handles Player collisions. Other collisions are handled by the corresponding services. If those events have an impact on a player, the User service will get notified through the UserEvents topic.

Rendering:

This service is responsible for reading the current Grid from the GridPositions-Topic, enhance it to have all needed informations of a game board and publishing that to all active clients. It also needs to be connected to the Gateway to be reachable by clients. It only needs to provide a subscription for rendering updates.

Statistics:

This service is responsible for collecting all the game data and providing statistics for different aspects (like biggest player, longest living player, overall consumed food etc.). First of all it should provide a leaderboard and can be enhanced later. Therefore it reads from the GridPositions-Topic but might need to read from all of the other Topics later for further statistics. It also needs to be connected to the Gateway, where it provides subscriptions for these statistics.

Worker-Services that are not connected to the API-Gateway:

Food:

The Food service is responsible for generating the food nodes players can consume to grow. It needs to be aware of all currently active food nodes, so that there isn't an endless amount of those active at the same time. Therefore it needs to know when a player consumed food to reproduce the same amount. It only handles Food collisions. When a user consumed food, this service als notifies the User service about how much a user should grow (through the UserEvents-Topic). Every time the Food-Nodes change, it publishes all active Food-Positions as a group to the NodePositions-Topic.

Obstacle:

The Obstacle service is responsible for generating all obstacles on the game board. First, this will be viruses and warp-fields, but any other obstacle that might be of interest can be added later. It publishes the obstacle positions as a group to the NodePositions-Topic and handles collisions with those obstacles. If those collisions have an impact on a player, this service will notify the User service through the UserEvents-Topic (e.g. if a player collided with a virus and has to shrink or be destroyed, or if a player has to be warped to the center).

Items:

The Item service is responsible for generating all items on the game board, which provide different effects on collision. At the moment these are invulnerability, a higher speed and a higher grow factor when eating food. It publishes the item positions as a group to the NodePositions-Topic and handles collisions with those items. If those collisions have an impact on a player, this service will notify the User service through the UserEvents-Topic.

NodeCollector:

This service is supposed to collect all game nodes published by other services to the NodePositions-Topic. Periodically it merges their current state to a unified data structure (Grid / Columns-Rows), that is optimized for collision detection or rendering and publishes these structures to the GridObjects-Topic. Therefore it first needs to identify the biggest node, which will either be a player or an obstacle, and then calculate the minimum cell size and grid. It then needs to assign nodes to their corresponding grid cells and create the message for the topic.

Collision:

This service is responsible for detecting collisions between nodes, which it gets through the GridObjects-Topic. Therefore it takes the player nodes as a basis for doing the collision checks. Because every game object is a node, this service does not need to know if those checks happen with another user, food or an obstacle and can handle all of them at once. It also checks for boundary hits. If it detects a collision it publishes an event containing the two colliding nodes to the Collisions-Topic. The first node is always the player that was focused, and the second one the node this player collided with. Food, Obstacle and Item service therefore only need to check if the second node is of their corresponding type and the user services only handles changes to the first node (if both nodes are players) to not do duplicate changes.