Search code examples
javawebsocketobject-oriented-analysisevent-driven-design

OOA&D / Java / Software Architecture - advise on structuring event handling code to avoid a complicated data flow


I've implemented the producer/consumer paradigm with a message broker in Spring and my producers use WebSocket to extract and publish data into the queue.

A producer is therefore something like: AcmeProducer.java handler/AcmeWebSocketHandler.java

and the handler has been a pain to deal with.

Firstly, the handler has two events: onOpen() onMessage()

onOpen has to send message to the web socket to subscribe to specific channels onMessage receives messages from WebSocket and adds them into the queue

onMessage has some dependencies from AcmeProducer.java, such as it needs to know currency pairs to subscribe to, it needs the message broker service, and an ObjectMapper (json deserializer) and a benchmark service.

When messages are consumed from the queue they are transformed into a format for OrderBook.java. Every producer has its own format of OrderBook and therefore its own AcmeOrderBook.java.

I feel the flow is difficult to follow, even though I've put classes for one producer in the same package. Every time I want to fix something in a producer I have to change between classes and search where it is.

Are there some techniques for reducing the complicated data flow into something easy to follow?

As you know, event handlers like AcmeHandler.java hold callbacks that get called from elsewhere in the system (from the websocket implementation) and hence they can be tricky to organize. The data flow with events is also more convoluted because when handlers are in separate files they cannot use variables defined in the main file.

If this code would not use the event driven paradigm the data flow would be easy to follow.

Finally, is there any best practice for structuring code when using web sockets with onOpen and onMessage? Producer/Consumer is the best I could come up with, but I don't want to scatter classes of Acme in different packages. For example, the AcmeOrderbook should be in a consumer class, but as it depends on the AcmeProducer.java and AcmeHandler.java they are often edited at the same time, hence I've put them together.

As the dependencies inside every WebSocket handler are the same (only different implementations of those same interfaces) I think there should be only one thing injected, and that will be some Context variable. Is that the best practice?


Solution

  • I've solved it using Message Dispatcher and Message Handlers.

    The dispatcher only checks if the message is a data message (snapshot or update) and passes control to the message handler class which itself checks the type of the message (either snapshot or update) and handles that type properly. If the message is not a data message but something else the dispatcher will dispatch the message depending on what it is (some event).

    I've also added callbacks using anonymous functions and they are much shorter now, the callbacks are finally transparent.

    For example, inside an anonymous callback function there is only this:

    messageDispatcher.dispatchMessage(context);
    

    Another key approach here is the use of context.MessageDispatcher is a separate class (autowired).

    I've separated orderbook into its directory inside every producer.

    Well, everything requires knowledge of everything to solve this elegantly.

    Last pattern for solving this: Java EE uses annotations for their endpoints and control functions such as onOpen, onMessage, etc. That is also a nice approach because with it the callback becomes invisible and onOpen / onMessage are automatically called by the container (using the annotation).