1

I am building a multiplayer game using Java for server. Currently, I am using a single class file to store player data and process the data. I am a beginner, so I had no idea that this is a bad practice. http://howtodoinjava.com/best-practices/5-class-design-principles-solid-in-java/ This article helped me to understand I am breaking the rule of 'Single Responsibility principle'.

This is how my code looks now.

public class PlayerSession{

    String playerId;
    String playerName;

    // 20+ player data fields, which I am trying to reduce
    // and keep only the most used data

    public void messageProcessor(JSONObject clientRequest) throws JSONException{

        switch(clientRequest.getString("task")){            
        case "login": loginProcess(); break;
        case "logout": logoutProcess(); break;

        //50+ different actions 
        }
    }

    public void populateSessionData(String playerId){
        // populate player data from database
    }

    private void loginProcess(){
        //Process login
    }

    private void logoutProcess(){
        //Process logout
    }

    //20+ other methods which do entirely different tasks.
}

As we add more features, the class will become extremely difficult to maintain and modify. Now I am trying to decouple this class into two different class. One, just to store Player data and an another one to handle behavior as shown below.

public class PlayerSession {

    final TaskHandler taskHandler = new TaskHandler();

    public void messageProcessor(JSONObject clientRequest) throws JSONException {

        switch (clientRequest.getString("task")) {
        case "login":
            taskHandler.loginProcess();
            break;
        case "logout":
            taskHandler.logoutProcess();
            break;

        // 50+ different actions
        }
    }
}

public class PlayerData {

    String playerId;
    String playerName;

    // 20+ player data fields, which I am trying to reduce
    // and keep only the most used data

    public void populateSessionData(String playerId) {
        // populate player data from database
    }
}

public class TaskHandler {

    final PlayerData player = new PlayerData();

    private void loginProcess() {
        // Process login
    }

    private void logoutProcess() {
        // Process logout
    }

    // 20+ other methods which do entirely different tasks.
}

And this design results in 2 additional object creation for a single client i.e, PlayerData and TaskHandler. For a server of 10,000 concurrent client, will this become an issue? Is this the right way to do it? If not, what is the best approach for a scenario like this?

Somewhere I read that objects just to save data is not a good approach. Is that right?

Kalesh Kaladharan
  • 1,038
  • 1
  • 8
  • 26

2 Answers2

0

You need to do a lot things here:

PlayerSession class:

Why do you parse JsonObject here? you need to create class that named ClientRequest and all work will be done inside it. Place all you need to want from client request to this class and only that methods that will be give ready to use something don't pull data from object and calculate manually in your code this is procedural way. Also rename PlayerSession to Session.

PlayerData class

First of all rename it to Player class it is represent Player don't Player data. Don't do any database relation actions in Player. Also after creation you need to have ready to use Player, here you create instance and after that populate it with data, better to do it in constructor.

TaskHandler class

Don't create class that do many thing, btw you almost in right way, simple create interface Task or Action that will be have only one method performTask() and create many implementations as LoginTask, LogoutTask etc. Maybe you need to have fabric that give you instance of particular action also that help to get rid of manually create concrete implementations and you will be do it in more polymorphic way.

public class Session {

    private final SessionActions actions;
    private final RequestFabric requests;
    private final Player player;

    public Session (SessionActionFabric actionsFabric,
                    RequestFabric requests, Player player) {
        this.actionsFabric = actionsFabric;
        this.requests = request;
        this.player = player;
    }

    void login() throws LoginException {
        Request request = request.createRequest();
        SessionAction login = actions.createActions("login", player, request);
        login.performAction();
        //something like that it's depends on whole picture of your project and maybe changed
    }

    //etc.
}

public interface Request {

    public performRequest(Player player, /*request data your api specs here*/) throws RequestException;
}

public class Player {

    private String id;
    private String name;

   public Player(String id, String name){
       this.id = id;
       this.name = name;
   }

   //getters, setters 
}

interface SessionAction {

    void performAction() throws SessionActionException;
}

class LoginAction implements SessionAction {
    private final Player player;
    private final Request request;


    LoginAction (Player player, Request request) {
        this.player = player;
        this.request = request;
    }

    void performAction() {
        // do you things here
    }

}

Q For a server of 10,000 concurrent client, will this become an issue? Is this the right way to do it? If not, what is the best approach for a scenario like this?

A Don't mind about performance best pay attention to good design, if you have issues with performance you almost always find the way to improve it with good design (caching, pooling, etc.)

QSomewhere I read that objects just to save data is not a good approach. Is that right?

A you are righ, this calls anemic model, (data and methods to process it are separate) but in present it very popular.

fxrbfg
  • 1,756
  • 1
  • 11
  • 17
  • I like this approach and I was thinking about designing something similar to this. But I want to avoid too many object creation. There will be requests every 2 seconds from client. So creating an action object for every two seconds just for a single client will soon result in OutOfMemoryException. Correct me if I am wrong. – Kalesh Kaladharan Jun 20 '17 at 17:18
  • Client and server communications are made through JSON. That's why the JSONObject. All the naming used in the question is for better understanding of readers. About the Player class, you are right. I have to populate data in the constructor itself. – Kalesh Kaladharan Jun 20 '17 at 17:23
  • @KaleshKaladharan You need to better understand how java works, especcially you need to read more about garbage collection. Creation one object in 2 second is completely nothing. When in method you create object, use it and exit from method this object garbage collected, OfMemoryException doesn't thrown. P.S. if you accept answer mark it as accepted. – fxrbfg Jun 20 '17 at 17:37
  • Thanks for your feedback. One object in 2 seconds per client. For 10,000+ concurrent clients it will be 5,000 object creation per second just for the action interface. There will be many more object created for completing the tasks. Will this be an issue? Yes, you are right. I should learn more about garbage collection. – Kalesh Kaladharan Jun 20 '17 at 18:19
  • @KaleshKaladharan try to write simple test that create 5,000 object in one second, connect to jvm from VisualVm (find it in bin directory of your jdk installation) and see how garbage collector works and how many memory your test utilize. – fxrbfg Jun 20 '17 at 18:26
  • Looks like I have to learn more about garbage collectors. I've read somewhere that creating too many objects in short span of time is bad for performance. I never asked how many is too much? Thanks for your reply. I'll test it as you said. – Kalesh Kaladharan Jun 20 '17 at 18:44
  • 1
    I have tested as you said. It looks like, creating temporary objects like these have little effect on JVM. Thanks. – Kalesh Kaladharan Jun 21 '17 at 11:01
0

Based on your discussion with Joy. If you are worried that Action class like LoginAction will be created every 2 seconds. Use singleton pattern, have static final instance of action class and one static getter for that instance (LoginAction.getInstanceOfLoginAction()) and use it every time instead of creating new ones. But make sure your action classes are stateless not stateful!!

class LoginAction implements SessionAction {
  private static final LoginAction loginAction = new LoginAction();
  public static LoginAction getLoginAction() {
      return loginAction;
  }

  void performAction(Player player, Request request) {
    // do you things here
  }

}

One more thing use a factory(see factory pattern) for getting implementation of Action interface as per request.

Ashutosh Jha
  • 1,465
  • 1
  • 17
  • 28
  • https://dzone.com/articles/stateful-or-stateless-classes check this link once for stateless vs stateful. – Ashutosh Jha Jun 20 '17 at 19:08
  • But, what will happen when 100 concurrent clients try to access the object. LoginAction should be able to verify client using a token and populate the data on their respective sessionData object. Can you please show some example code to do this. Thanks. – Kalesh Kaladharan Jun 20 '17 at 19:14
  • The way i have written LoginAction without private fields, it work as expected even if there are 100 concurrent client. Now if you want to send them on different threads then I want to know are you using java apis like Future and do you also use RxJava(wonderful for parallelisation). – Ashutosh Jha Jun 20 '17 at 19:17
  • I am using Netty which is asynchronus NIO framework and uses Future. Looks like I am complicating things a bit too much. Currently, I have everything working under a single object. Tested it for few thousand concurrent connections. But now I want to add more features to the application. So the class is getting difficult to maintain. I am after a good design without compromising PERFORMANCE. – Kalesh Kaladharan Jun 20 '17 at 19:29
  • Ok then use ExecutorService and if you are working on latest java version use CompletableFuture. – Ashutosh Jha Jun 20 '17 at 19:33
  • If you have any more doubt, please let me know. We can discuss what is happening inside that method too(performAction()). – Ashutosh Jha Jun 20 '17 at 19:39
  • Yeah, thanks for your feedback. I am a self taught programmer and this is my first project for production. So I want to make it as clean as possible, highly efficient and scalable. I want to know how applications with client sessions are designed. Like things which application does after client connects, how messages are processed. Remember, these are all with respect to Websockets and not REST. If you could guide me to some source, it'll be really helpful. Have a look at this question also. https://stackoverflow.com/questions/44373406/how-to-process-websocket-messages-from-client-in-java – Kalesh Kaladharan Jun 20 '17 at 19:49
  • You need to search that a lot and read alot. But one thing i want to add. What i have suggested will have same performance as yours. But if you don't use async API's like future etc you are definitely having lower performance than what you can. – Ashutosh Jha Jun 20 '17 at 19:54