Looking to craft a quick, scam-protected game? Explore Kafka!

In the fast-paced world of gaming, creating a game that is both high-performing and secure is a challenging yet rewarding task. One of the key technologies that can help you achieve this is Apache Kafka. In this comprehensive guide, we will explore how to leverage Kafka and Spring Boot to build a game that meets these demanding requirements. From setting up Kafka to implementing advanced game logic and ensuring secure data handling, this guide has got you covered.


Introduction to Apache Kafka

Apache Kafka is a distributed event streaming platform that is both highly efficient and reliable. It is designed to handle large volumes of data from multiple producers and distribute it to multiple consumers. Kafka is widely used for real-time data processing applications, including games, where low-latency and high throughput are critical.


Key Features of Apache Kafka:

  • High Throughput
  • Low Latency
  • Scalability
  • Durability
  • Fault Tolerance


Setting Up Apache Kafka

Kafka operates as a separate process on your server and requires independent installation. It comes with a complete ecosystem including Zookeepers, Brokers, Schema Registry, CLI tools, and Control Center. To use Kafka’s basic functionalities suitable for game development, you can install the community version running on Docker.


Steps to Set Up Kafka Using Docker:

  1. Clone the repository: git clone https://github.com/confluentinc/cp-all-in-one.git
  2. Navigate to the directory: cd cp-all-in-one/cp-all-in-one-community (or cp-all-in-one/cp-all-in-one)
  3. Run Docker Compose: docker-compose up -d --build
  4. Verify that the services are running: docker ps

Once set up, you will have the Kafka ecosystem running and ready to handle your game’s data efficiently.


Integrating Kafka with Spring Boot

Spring Boot makes it easy to build production-ready applications and to integrate with Kafka. To use Kafka in a Spring Boot application, you need to add the necessary dependencies and configure Kafka properties.


Dependencies to Add:



dependencies {
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.apache.kafka:kafka-streams'
implementation 'io.confluent:kafka-json-serializer:5.5.1'
}

Configuring Kafka Properties:



@Bean
public Properties kafkaProperties() {
Properties props = new Properties();
props.setProperty(BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.setProperty(ACKS_CONFIG, "all");
props.setProperty(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.setProperty(VALUE_SERIALIZER_CLASS_CONFIG, KafkaJsonSerializer.class.getName());
return props;
}

This basic configuration enables your application to connect to Kafka, whether it’s running on the same server or a remote one.


System Architecture

In the proposed architecture, the game application is divided into three primary components:


  1. Data Bus: Handles incoming messages and saves them to Kafka topics.
  2. Monitoring Game Progress: Uses Kafka Streams to aggregate game data and determine game progress.
  3. Game Post-Processing: Validates completed games and persists results to MongoDB.

Data Bus Implementation

Incoming messages (blocks) are consumed by a REST controller. Three types of events are handled: GameStart, NextLevel, and GameEnd.




@RestController
public class GameController {
@PostMapping("/game/start")
public Mono startGame(@RequestBody @Valid GameStart level) {
return gameService.startGame(level);
}

@PostMapping("/game/level")
public NextLevelInfo nextLevel(@RequestBody @Valid NextLevel level) {
return gameService.nextLevel(level);
}

@PostMapping("/game/complete")
public NextLevelInfo completeGame(@RequestBody @Valid GameEnd level) {
return gameService.completeGame(level);
}
}

The messages are persisted in Kafka quickly and reliably.


Monitoring Current Game Progress

We utilize Kafka Streams to read all messages from the blocks topic, group them by game GUID, and aggregate them into a GameAggregate. If the game is complete, the data is persisted to the games topic.




StreamsBuilder streamsBuilder = new StreamsBuilder();
streamsBuilder.stream("blocks", Consumed.with(Serdes.String(), GSerdes.BlockModel()))
.groupByKey()
.aggregate(GameAggregate::new, (gameGuid, newBlock, gameAggregate) -> {
gameAggregate.add(newBlock);
return gameAggregate;
}, Materialized.with(Serdes.String(), CustomSerdes.GameAggregate()))
.filter((gameGuid, gameAggregate) -> gameAggregate.isComplete())
.toStream()
.to("games", Produced.with(Serdes.String(), Serdes.GameAggregate()));

Topology topology = streamsBuilder.build();
KafkaStreams kafkaStreams = new KafkaStreams(topology, props);
kafkaStreams.start();

Game Post-Processing

This component reads completed games from the games topic, validates them, and persists valid results to MongoDB.




StreamsBuilder streamsBuilder = new StreamsBuilder();
streamsBuilder.stream("games", Consumed.with(Serdes.String(), GSerdes.GameAggregate()))
.peek(gameAggregateHandler::handle);

Topology topology = streamsBuilder.build();
KafkaStreams kafkaStreams = new KafkaStreams(topology, props);
kafkaStreams.start();

The application effectively handles high traffic loads, thanks to Kafka Streams’ ability to process events efficiently.


Results and Performance Comparison

We tested three approaches—Blocking API, Non-blocking API with Thread Pool, and Kafka Streams—using the Gatling tool for about 15,000 requests.


Blocking API

While blocking API is easy to implement, response times were not acceptable, with 60% of responses taking over 1200ms. This delay severely impacts gameplay fluidity.


Non-blocking API and Thread Pool

Using a thread pool improved performance, with 75% of responses taking less than 800ms. While better, it still did not meet the required performance levels for an optimal gaming experience.


Kafka Streams

With Kafka Streams, almost 100% of requests were completed in under 800ms, making it the fastest and most efficient solution.


Based on our tests, Kafka Streams is the best choice for building a high-performance, secure game. Its ability to handle events in a non-blocking, highly efficient manner ensures that your game can handle high traffic loads while maintaining low latency.


For more details on building modern architectures and big data processing, explore our other articles and resources.


Want to stay updated with the latest in software engineering? Subscribe to our newsletter.