SkillAgentSearch skills...

Ratpong

WEB Pong written in Plain Java

Install / Use

/learn @javaFunAgain/Ratpong
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ratpong

Codacy Badge Build Status

This is POUNK game server implementation which is network version of a classic PONG.

For client code go to : https://github.com/jarekratajski/scalajspounk

java 9

In order to run this code in Java 9 please add this switch to jvm. --add-modules java.activation

Running?

Just call gradle run Navigate to http://localhost:9000

As a user you can register with user and password. After that you can login. You can create new game and enter game... but once another player joins it. Yes you need 2 players,
you may open second browser window and register a second player.

Controls:

  • Q move paddle up,
  • A move paddle down,

Purpose

The goal of this project is to prepare clean Java example of non blocking server architecture. System uses as little mutability as possible, no magic frameworks, application servers and simply starts with main method ( as Fat Jar).

For a moment system uses such technologies:

  • Ratpack providing REST server library,
  • JavaSlang for immutable data structures (vavr :-) ),
  • Airomem / prevayler for simple persistence
  • Junit5 for simple tests,

Notice that system does not use any special dependency injection frameworks or containers. It is 2017 - we do not need it anymore! :smile:

Legal issue

Please, bear in mind that PONG is a registered trademark that probably belongs to Atari SA.

Architecture

System is divided into 3 main modules.

  • users - user registration and login/session logic
  • games - created games list, states of games
  • scores - registered scores

Typical module consists of:

Concepts

Dependency injection

Code does not use any dependency injection container. For it is 2017. Once you use modern web server such as Ratpack you do not have technical limitations that happen with Servlet architecture that make use of dependency injection by container handy. You are in full control of our objects creation and you can use this power. :metal:

It does not mean there is no dependency injection working. It is. Only done the easy way: with constructor. (Notice that if you follow Oliver Gierke advice field injection evil you get almost same code...)

Check for instance ScoresService class. It has one dependency ScoresRepositoryProcessor which allows us to inject different persistence engine if needed (or for tests).

     public ScoresService(ScoresRepositoryProcessor nonBlockingRepo) {
        this.nonBlockingRepo = nonBlockingRepo;
    }

Btw.: ScoresRepositoryProcessor has indeed further dependencies... (but this is not the problem).

In order to not get lost in "new" hell there is a factory which handles some modules defaults. See ScoresModule. This is exactly one place which decides explicitly what is default persistence etc. This code is more or less same as you would define in spring-beans.xml or with @Component annotations. This time however fully type safe, debuggable, testable. And without magic.

You can check the same concept working with more complicated classes as GamesService/GameModule More dependencies? No problem. Use java to control them all. :beers:

Service

It is easy to define REST api. Lets talk about Users.

Thare are 2 operations there. POST /users/USER_ID -register new user by POSTing data (password) POST /sessions/USER_ID - login user by posting password (it is POST because we create session)

There is usersApi method in UsersService which basically defines how to handle both operations. See how easy we do it with lambdas: :sunglasses:


public Action<Chain> usersApi() {
        return apiChain -> apiChain
                .prefix("users", users())
                .prefix("sessions", sessions());

    }

    private Action<Chain> users() {
        return chain -> chain
                .post(":id", addUser());

    }

Then real addUser implementation can be written:


private Handler addUser() {
        return ctx -> {
            final String userId = ctx.getPathTokens().get("id");
            ctx.parse(NewUser.class).then(
                    newUser -> {
                        final Promise result = Promise.async(
                                d -> d.accept(usersRepo.addUser(userId, newUser.password).thenApply(Jackson::json)
                                ));
                        ctx.render(result);
                    }
            );
        };

See how simple is to convert JSON from input (ctx.parse) and then our result to output JSON (Jackson::json).

Promises

One thing that may look unfamiliar in the code above is this Promise thing. :confused: Ratpack is a non blocking server. What does it mean: one should not block processing of request (by calling and waiting for IO ). So if you want to ask the database and then wait for result you are generally destroying the whole concept of non blocking architecture. :boom:

So how to work with that?

The answer are Promises (which are some form of Futures that you could have probably heard about). Instead of just doing the blocking thing we return Promise object that will be completed somewhere in the future. Possibly when result from our database is read. Ratpack makes life easy here providing handy implementation of Promise. ctx.render accepts generally a String in case we know how to answer or a Promise which will be resolved somewhere in the future (which is more likely).

So what is this?

final Promise result = Promise.async(
                                d -> d.accept(usersRepo.addUser(userId, newUser.password).thenApply(Jackson::json)
                                ));

This is in fact conversion between JAVA promise called CompletableFuture and Ratpack Promise. (Ratpack was created before CompletableFuture was defined in Java API). (Btw. this operation can be easily extracted)

So: you know how to render Promise (or CompletableFuture) - but how to get it?

Here comes to help the class called UserRepositoryProcessor. The responsibility of this class is to cooperate with a blocking persistence engine and delegate processing to other threads so that the request processing thread is not blocked.

You may wonder: what sense does it make? - not blocking the request thread but instead to block some other thread... where is the point?

That is good point indeed. If you had some kind of nonblocking DB such as Cassandra or even MongoDB you could leverage that in be truly non blocking way. But probably you do not have. Still nothing wrong happens! It is possible to use Blocking IO with non blocking server and still get some gain.

What you can do (win) is the control of your application threads! Imagine 5000 users trying to get access to the rest service simultaneously. In a classical (blocking) architecture it would mean 5000 threads are created, all of them making connection to database, waiting for answer, holding memory.
This does not sound good. Imagine what your database feels.

As an experienced human you know that it is typically better to do tasks one by one - than to start thousands of them concurrently.

This is exactly what can be done with blocking DB. You can limit how many concurrent queries (or generally operations) you perform and simply queue all the rest. This is what "Processor" class does. It uses very nice tool from standard Java lib called Executor. Simply create an executor and tell how many threads should it use (lets say one!). Then just queue all subsequent operations using it:

 ```
 writesExecutor.execute( ()-> {
             result.complete(this.usersRepository.addUser(login, pass));
         });
```         

See? That executor operation returns CompletableFuture - and this is exactly what we need.
Notice also that this is called wr

Related Skills

View on GitHub
GitHub Stars39
CategoryDevelopment
Updated1mo ago
Forks16

Languages

JavaScript

Security Score

90/100

Audited on Feb 5, 2026

No findings