2

I wrote about Java sockets and networking a few weeks ago. As a follow up, how would you manage state in a scenario with a server and multiple clients? A few solutions I've thought of:

Making the client and the server share a common class that makes the both of them know what can and can not be done.

Having a "Message" class which will contain a static method for parsing a received payload and return a Message instance with intent and methods to resolve intent (i.e. sending data to the client).

Perhaps both concepts should be used, or perhaps none. I'd like to hear your ideas :)

Comments
  • 1
    If you have request/response model then I would make contracts (model classes) shared between client and server. And provide class to client for calling server e.g. MyProtocolClient. This client would have methods for sending and receiving individual messages and wil ltake care of whole socket logic and serialization.

    public CreateUserReponse CreateUser(UserModel);

    Where UserModel is DTO which get serialized and and flushed to socket (server also knows UserModel), server responds, client deserializes and produces CreateUserResponse.

    This way, programmer is completely abstracted from your protocol, does not have to deal with json/binary etc...

    I would do this approach for request/response oriented protocol (RPC/REST). Ofc your use case might be different.
  • 1
    Socket is just a transport layer. It is inner-workings, completely irrelevant to the client. As @meyhem has already pointed out, build a contrsct layer [interface, describing your service] and make 2 implementations: server-side and client side. Like

    public interface AccountApi {
    Account login(String name, String pw);
    }

    public AccountApiSocketServer implements AccountApi { /*...*/ }

    public AccountApiSocketClient implements AccountApi { /*...*/ }

    these names assume you will implement serializers and transport layer in those implementations. A more flexible way would be to make

    public AccountApiServer implements AccountApi {
    public AccountApiServer(ApiTransport transport) {this.transport=transport;}
    /*...*/
    }

    which allows you to pass a transport layer implementation at construction. It makes code unit-testable.

    Adding ability to configure serializers separately adds better extensibility at a price of more complex init configuration. If your use case does not require that - skip it.

    Then pass the interface to your client-part. Implemebt transport layer and that interface. Now your client will be able to simply

    Account acc = accountApi.login("user1","pass123");

    without bothering how, why, where this info is transferred, what conversions are made, etc. Your service layer does not need to know thise details. But it needs to know your service's contract. That's why you need that interface shared btwn server and client
  • 1
    As a bonus, interfaces will keep you informed when you have breaking changes. Say you have that AccountApi interface with a login() method and at some point you decide you want to change that method to accept 3rd param -- int sessionTimeout. You will change that interface, you will change server-side implementation. And your compilation of client-side imple will fail, because it also needs to be updated. Your client and server are bound through that interface and if one side makes changes to it - the other must change accordingly. This is a great way to keep lots of "whoops, I forgot" bugs away :)

    EDIT: P.S. Do not make that parser static :) You might want to make some changes in the future and static methods will hold you back.
  • 1
    @netikras Thanks for a really good explanation, that really helps :) Fact is, that clarifies quite a few things I've been wondering about.

    That last thing about not making the parser static. Why? It shouldn't hold any real state except during parsing, right? I guess if it grows (which it most certainly will) it might be a bit neater to have it in a separate class, but what would be the issue with having it static?
  • 1
    @ScriptCoded Since you are detatching transport from your service, you can now have unlimited implementations of transport layer. For instance you might have:
    ApiTransportTcp
    ApiTransportMQ
    ApiTransportNFS
    ApiTransportREST
    ApiTransportSOAP
    ApiTransportLocal
    etc..

    What data will you be transferring? Will you be transferring JSON through a SOAP transport? Will you be serializing into XML and deserializing to POJOs if you have a Local impl which can work perfectly with POJOs in a first place?

    Who says you cannot pass XML or JSON through a raw TCP transport impl?

    Embedding serialization into static context will freeze your freedom of configurations. And is does not matter whether you have state or not. Think more like: "Is this component likely to be extended/overriden after some time?".

    In fact as many of the service objects as possible should be stateless :) State should usually be kept in data storage (DB, cache, etc.)
  • 1
    @netikras Ahh, I see. That makes sense... Thanks so much again, this is really clearing some stuff up. I wish I could find more resources on this, but I guess I've found that some things are simply best passed along face to face (or, well, nore really face perhaps...) :)
  • 1
    @ScriptCoded clean code and design patterns :) Look them up. Robert C. Martin (Uncle Bob)

    People like bashing on those two, IDK why, maybe these are only lazy and amateur devs who try to swim against the current. Clean code and design patterns (which are a part of clean code) are real savers IMO.
  • 0
    @netikras I'll definitely look that up. Thanks!
Add Comment