Skip to content

Correctly abstracting NetCom2

Thorben Kuck edited this page Sep 19, 2018 · 5 revisions

Why should we abstract NetCom2

Imagine you create a big application, maybe with multiple people and coountless lines of code. After a while, your group decides to switch out NetCom2, because you want to change your application to a Client-Only application, or maybe because the group wants to use another Framework. If you did not abstract the Framework, maybe even use the Session in your bussines-logic or maintain NetCom2 within other modules, switching this framework out for another is a real pain in the ***.

The Example code i am providing is only that, an example. You can abstract the Framework in countless ways.


Before we start to abstract NetCom2, lets define some Objects that we will need.

First of, we create an interface, called User, which will be used, to abstract the Clients.

public interface User {

    void notify(Object o);

    boolean isLoggedIn();

    void logIn();

    void logOut();

    String getNickName();

    void setNickName(String name);

}

For encapsulating NetCom2, we create the following Class, which basically encapsulates the Session from the connected client.

import com.github.thorbenkuck.netcom2.network.shared.Session;

class NetCom2User implements User {

    private final Session session;

    NetCom2User(final Session session) {
        this.session = session;
    }

    @Override
    public void notify(final Object o) {
        session.send(o);
    }

    @Override
    public boolean isLoggedIn() {
        return session.isIdentified();
    }

    @Override
    public void logIn() {
        session.setIdentified(true);
    }

    @Override
    public void logOut() {
        session.setIdentified(false);
    }

    @Override
    public String getNickName() {
        return session.getProperties().getProperty("nickname", "NO_NICKNAME");
    }

    @Override
    public void setNickName(final String name) {
        session.getProperties().setProperty("nickname", name);
    }
}

Now, we can create a Client and a Server to use inside of our Code

import com.github.thorbenkuck.netcom2.exceptions.ClientConnectionFailedException;
import com.github.thorbenkuck.netcom2.exceptions.StartFailedException;
import com.github.thorbenkuck.netcom2.network.server.ServerStart;
import com.github.thorbenkuck.netcom2.network.shared.Session;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;

public class Server {

    private final ServerStart serverStart;
    private final RemoteObjectRegistration registration;
    private final ExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
    private final Map<Session, User> mapping = new HashMap<>();

    public Server(int port) {
        serverStart = ServerStart.at(port);
        registration = RemoteObjectRegistration.open(serverStart);
        serverStart.addClientConnectedHandler(this::connected);
    }

    private void connected(Client client) {
        mapping.put(client.getSession(), new NetCom2User(client.getSession()));
        client.addDisconnectedHandler(disconnected -> mapping.remove(client.getSession()));
    }

    public void launch() {
        try {
            serverStart.launch();
        } catch (StartFailedException e) {
            throw new IllegalStateException(e);
        }

        threadPool.execute(this::acceptClients);
    }

    private void acceptClients() {
        try {
            serverStart.acceptAllNextClients();
        } catch (ClientConnectionFailedException e) {
            e.printStackTrace();
        }
    }

    public void ifObjectArrieves(BiConsumer<User, Object> consumer) {
        serverStart.getCommunicationRegistration()
            .addDefaultCommunicationHandler((session, o) -> {
                User user = mapping.get(session);
                consumer.accept(user, o);
            });
    }

    public <T> void exposeToClient(T o, Class<T> identifier) {
        registration.register(o, identifier);
    }
}
import com.github.thorbenkuck.netcom2.exceptions.StartFailedException;
import com.github.thorbenkuck.netcom2.network.client.ClientStart;

import java.util.function.BiConsumer;

public class Client {

    private final ClientStart clientStart;
    private final Sender sender;
    private final RemoteObjectFactory factory;

    public Client(final String s, int port) {
        this.clientStart = ClientStart.at(s, port);
        this.sender = Sender.open(clientStart);
        this.factory = RemoteObjectFactory.open(clientStart);
    }

    public void connect() {
        try {
            clientStart.launch();
        } catch (StartFailedException e) {
            throw new IllegalStateException(e);
        }
    }

    public void send(Object o) {
        sender.objectToServer(o);
    }

    public void ifObjectArrives(BiConsumer<Client, Object> objectConsumer) {
        clientStart.getCommunicationRegistration().addDefaultCommunicationHandler(o -> objectConsumer.accept(this, o));
    }

    public <T> T askFromServer(Class<T> clazz) {
        return factory.create(clazz);
    }
}

So, what we did here is, that we abstracted the whole framework and limited the access to it.

At the Client class, we introduced a Consumer, that points to the received Object and the Client-class itself.

At the Server class, we introduced the User interface, which will be injected, whenever a Client connects, but removes the User-instance once it disconnects. This is injected, whenever we receive an Object. So, we basically can expand on this further:


Considerations

This abstraction of NetCom2 greatly reduces the access to certain APIs. You basically only have an over-network EventBus. This might be a good thing (and exactly what you want), but other things are harder to abstract.

For example, it is harder to come up with a way of abstracting the advanced RMI API.

Creating a custom Connection mechanism (abstracted, i might add) is more difficult. This is one more reason, for limiting yourself greatly, when using Connections. You could of course work with custom Connection routs or something.