1
\$\begingroup\$

Background:

I have a TCP online multiplayer game server, which the player requests to log-in. However, the game server does not handle the authentication part of the login process, instead, it uses an outer micro-service which is an HTTP server to see if the user can authenticate with the given credentials.

Now why is that a problem? - Well I use netty.io networking framework, which is a non-blocking framework, it handles all connections in a sequence on the same thread. Since I don't want to cause any delays, I will not send an HTTP request on the same thread because I don't want to stop it.

Instead I followed a pattern of callbacks like java script promise resolve-reject, and I Would like to hear what you think, and if it could be done better.

Don't mind the generic types, just take the basic idea of it:

First of all this is my ServiceProvider, the point of this class is to provide a singleton of each service that interacts with microservices such as UserService which is an HTTP server to handle everything related to user (authentication, data, etc):

public class ServiceProvider {

    private static ExecutorService executorService = Executors.newFixedThreadPool(ServicesConfig.SERVICES_THREAD_POOL_CAPACITY);

    private static UserService userService;

    /**
     * Provdes an user service singleton instance
     * @return  The user service
     */
    public static UserService getUserService() {
        if (userService == null) {
            userService = new UserService();
            userService.setBaseUrl(ServicesConfig.USER_SERVICE_BASE_URL);
        }
        return userService;
    }

    public static void submitServiceTask(ServicePromise task) {
        executorService.submit(task);
    }
}

Secondly is my UserService for example:

public class UserService extends HeroesHttpService {

    /**
     * Authenticates an user
     * @param username  The username
     * @param password  The password
     * @return  ?
     */
    public ServicePromise<?> authenticateUser(String username, String password) {

        ServicePromise<HttpResponse> promise = new ServicePromise<HttpResponse>() {
            @Override
            public void run() {
                HttpClient client = HttpClients.createDefault();

                HttpGet request = new HttpGet(getBaseUrl() + "/user/authenticate");
                try {
                    HttpResponse response = client.execute(request);
                    resolve(response);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };

        ServiceProvider.submitServiceTask(promise);
        return promise;
    }
}

Now this is my promise interface:

public abstract class ServicePromise<T> implements Runnable {

    private ServicePromiseCallback<T> callback;

    public ServicePromise<T> then(ServicePromiseCallback<T> cb) {
        this.callback = cb;
        return this;
    }

    @Override
    public void run() {
    }

    public void resolve(T data) {
        if (this.callback == null) {
            return;
        }
        this.callback.onResolve(data);
    }

    public void reject(T data) {
        if (this.callback == null) {
            return;
        }
        this.callback.onReject(data);
    }
}

And that's the callback interface:

public interface ServicePromiseCallback<T> {
    void onResolve(T response);
    void onReject(T response);
}

And finally, thats how I use it:

    ServiceProvider.getUserService().authenticateUser(player.playerName, player.playerPass).then(new ServicePromiseCallback<?> () {
        @Override
        public void onResolve(Object response) {

        }

        @Override
        public void onReject(Object response) {

        }
    });

Again, don't mind the types in resolve/reject, it's going to definitely be AuthenticateUserResponseDto and so on, but it doesn't matter right now

\$\endgroup\$
6
  • \$\begingroup\$ I don't understand the use case: user authentication should be a synchronized action. the client must block until user is authenticated. so what kind of "delay" do you want to resolve? \$\endgroup\$ – Sharon Ben Asher Dec 30 '19 at 9:44
  • \$\begingroup\$ The client waits for the response, the game server is not an HTTP server, it doesn't wait for a response by protocol, it will just receive a success packet once the process is finished, or failure packet. I don't want to stop the main thread that handles all users for this login request \$\endgroup\$ – Ben Beri Dec 30 '19 at 9:46
  • \$\begingroup\$ You have to look at it like this; You have 1000 users, logging in every time, if you will send a request or bulk of requests every X time, each user has to wait for each login to finish. but if you handle each login request (the call to the user HTTP server) on a different thread, this solves the issue of waiting \$\endgroup\$ – Ben Beri Dec 30 '19 at 9:47
  • \$\begingroup\$ for example player A tries to login, now player B tries to login, but the thread handles player A's http request, thread is locked, therefore B wont execute until A finishes, but what about C, D, E players which are already logged in? they will not receive an update, and in a game server it is important to send update packets otherwise the game will start "lagging" (unless the requests take less than 600ms which is the tick rate) in my game specifically \$\endgroup\$ – Ben Beri Dec 30 '19 at 9:49
  • 1
    \$\begingroup\$ please update the question with the new info. it is not specified that this is an online multi player scenario (I was thinking along the lines of online game service like kongregate) \$\endgroup\$ – Sharon Ben Asher Dec 30 '19 at 9:54
2
\$\begingroup\$

the ServiceProvider is supposed to provide a singleton instance of UserService but it does not follow all the principles of the Singleton design pattern. specifically, the locking mechanisms that prevent multiple threads from creating multiple instances of UserService (or rather, multiple calling of the constructor)

and while on the subject - why not use Dependency Injection?

also, reject(response) is never called

with regard to the issue of the "javascript-like promise resolve-reject": Java has such a mechanism: CompletableFuture where you supply a Runnable and for the callbacks there are thenApply() thenAccept() and thenRun() methods. exceptions are handled by exceptionally() method. There are other capabilities like chaining CompletableFuture instances (thenCompose().

\$\endgroup\$
12
  • \$\begingroup\$ So you are basically saying that multiple threads are going to use that user service reference provided, which may cause a conflict (because I don't synchronize them), should I make it a new instance of user service instead? And I dont use dependecy injeciton because the project is large, adding dependcy injection to it is an hassle, its either use it everywhere or nothing, can't break the convention \$\endgroup\$ – Ben Beri Dec 30 '19 at 11:13
  • \$\begingroup\$ Would love to see an example of firing a Runnable with thread pool Executors and received a CompletableFuture, I had issues with it which took me to a too complicated door \$\endgroup\$ – Ben Beri Dec 30 '19 at 11:14
  • \$\begingroup\$ UserService instance is static so there will always be one instance (if you have one class loader) but multiple threads may enter the if statement and call the constructor. You should look up singleton design pattern double checked locking mechanism. you should also be aware that in today's software applications singleton are injected. \$\endgroup\$ – Sharon Ben Asher Dec 30 '19 at 12:21
  • 1
    \$\begingroup\$ yes, yes, yes,. \$\endgroup\$ – Sharon Ben Asher Dec 31 '19 at 9:08
  • 1
    \$\begingroup\$ not sure about "before everything". the static block is executed by the class loader when the class is first referenced and is loaded into memory \$\endgroup\$ – Sharon Ben Asher Dec 31 '19 at 9:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.