I'm working on a client-server sort of protocol right now in Ruby. I've hit a snag where I'm not entirely sure how to proceed. Currently, I have a class, Remote that signifies a remote connection (for use through EventMachine). When Remote receives a message (i.e, network packet), it sends it off to the MessageDecoder, which returns the decoded message in a hash, like so (pseudo-code ahead)

{ source = <the_remote_connection>,
  message_code = 1, // An operation code that indicates how to respond to this message
  payload = {
    packet_specific_key = packet_specific_value // provided by the decoder

So, when I get this back from MessageDecoder, the Remote ships it off to its ConnectionListener, which needs to decide how to distribute that message across the application. Here's the issue: Each message might require a different scope of variables. For example, one might require the instance of a World object that the ConnectionListener doesn't know about (and rightfully shouldn't - the network code should not be coupled to the rest of the program)

So my question is: how can I implement a loosely-coupled system for distributing the reception of network-ignorant messages from a remote connection in my application that somehow magically retains scopes?

EDIT: For comments, example.

Let's assume I have a World object. The World knows about the entities within it - rocks, trees, you get the general idea. So, a command comes in from the Remote saying :DestroyRock - which should invoke World#destroy_rock. The World should not know where the command to :DestroyRock comes from. It should just know that World#destroy_rock was invoked on it.

The issue is, I need some way of finding a middle-man between ConnectionListener and World#destroy_rock. ConnectionListener definitely should not be aware of the World object, because if it is, that means I've now coupled my network code to my gameplay logic.. woops. That's pretty bad.

  • What do you mean by scopes? Where does the object come from and where do you want it to appear? – Jan Hudec Mar 10 '14 at 14:51
  • Bad wording, I guess. Long story short I'm asking for an algorithm/method that allows me to keep networking code and the rest of the code completely separate. – Dan Pantry Mar 10 '14 at 14:54
  • They obviously can't be completely separate, because they need to pass data between themselves. What did you mean by that World object? How would the ConnectionListener use it if it was allowed to use it? – Jan Hudec Mar 10 '14 at 15:19
  • Updating OP with example – Dan Pantry Mar 10 '14 at 15:36
  • Sounds like you need an event framework. The wiring of events can be done in configuration somewhere, outside the realm of either the network code or the objects that actually consume events. – user22815 Mar 10 '14 at 16:05

The ConnectionListener has to know about the World instance, because it needs to pass it the command. The ConnectionListener is already a middle man, adding another middle man won't solve anything. It should, however, only know it by interface. ICommandable or something. This would provide a method to pass the message to and return appropriate response, possibly an even to provide asynchronous response/message to client and possibly a filter which messages this object should handle.

Than in the initialization code you'll create the World instance and the ConnectionListener instance and register the world with the listener. Something like:


the filter probably makes more sense here than inside the object, so perhaps

listener.addCommandable(':DestroyRock', world);

(or with a list of commands or something).

I might also make sense to implement the interface not in the World object itself, but in an adaptor, so the World object does not need to know about the format of the command from network. Especially if there are other ways to send it commands like a stand-alone version that has both user interface and the engine in one process and does not use network.

Of course since Ruby is dynamically typed, an "interface" is just a documented set of methods.

  • The issue with ConnectionListener knowing about the interface of consumers of these messages is that ConnectionListener ends up depending on every consumer of a network event. Interface or not, this isn't very loosely coupled at all. It also doesn't make sense from a programming point of view - Why should the code that is responding to a network event need to know about the intricacies of the server? This violates SRP. – Dan Pantry Mar 10 '14 at 16:09
  • Also, ConnectionListener listens for connection events, such as Connected, Dropped, MessageReceived, Exception, etc. It doesn't exist to solely act as a middleman for messaging. Currently it has a dependency on a MessageDecoder which decodes the incoming messages, which is then returned back to the ConnectionListener, where the ConnectionListener has to dispatch the decoded message SOMEHOW. – Dan Pantry Mar 10 '14 at 16:18
  • @DanPantry: Yes. And it will dispatch it to "Commandable" (or "MessageProcessor" or something). And that can be another middle man, or it can be an interface implemented by the object implementing the logic. Another middle man won't achieve anything here, so it should be interface implemented by the object implementing the logic. – Jan Hudec Mar 10 '14 at 16:27
  • @DanPantry: Loose coupling does not mean the objects instances don't refer to each other. It means the object instances refer to minimal interfaces. – Jan Hudec Mar 10 '14 at 16:29
  • I understand that. I thought you meant that I would be directly passing in an instance of World without abstraction into CommandListener :-) which would mean that CommandListener would depend directly on World, rather than an abstractino. I think I will go for an eventbus style system because this is probably not the only event handling I will need. So I will inject an eventbus into CommandListener rather than individual event subscribers. – Dan Pantry Mar 10 '14 at 16:36

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.