I am trying to resolve a circular dependency between two components in my system. The Messenger
component is responsible for sending and receiving messages on a web socket. The Controller
component requires use of the Messenger
component to be able to send messages to connected clients because the Messenger
component knows about the connected sessions.
The Messenger
component has a dependency on the Controller
component to be able notify the controller of incoming messages.
The WebsocketHandler
is a framework interface that I have no control over in this case and calls the appropriate methods on my implementation based on the connected clients.
What architectural changes could I make to resolve this?
interface WebsocketHandler
{
void onConnect(Session session);
void onDisconnect(Session session);
void recieve(Object payload);
}
class Messenger implements WebsocketHandler
{
Controller controller;
Set<WebsocketSession> websocketSessions;
@Override
void onConnect(Session session)
{
sessions.add(session);
}
@Override
void onDisconnect(Session session)
{
sessions.remove(session);
}
@Override
void recieve(Object payload)
{
controller.messageReceived(payload);
}
// requires access to sessions to send messages
void send(Object payload)
{
for (WebsocketSession session : websocketSessions)
{
session.send(payload);
}
}
}
class Controller
{
Messenger messenger;
void messageReceived(Object payload)
{
// do something with payload
}
void notifyClients()
{
messenger.send(...);
}
}
-
I had the same problem: stackoverflow.com/questions/16971656/enum-of-enum-is-nullMarcelo Idemax– Marcelo Idemax08/09/2018 19:30:00Commented Aug 9, 2018 at 19:30
3 Answers 3
The simple solution to this is to recognise that Controller
doesn't need a dependency on Messenger
, it needs a dependency on send
. So provide it with that dependency:
interface Sender {
void send(Object payload);
}
class Messenger implements WebsocketHandler, Sender { ...
class Controller
{
Sender sender;
void messageReceived(Object payload)
{
// do something with payload
}
void notifyClients()
{
sender.send(...);
}
}
Now, Controller
just has a dependency on the Sender
interface, removing that circular dependency.
UPDATE
As the OP points out, the above removes the direct circular dependency between Controller
and Messenger
. However, if constructor injection is used, the dependency still exists at the point of creation of the objects: we need to create Sender
first to satisfy Controller
's need for a Sender
, but we need to create Controller
first as Messenger
needs to call controller.messageReceived(payload)
.
There are various ways of working around this, such as supporting property injection. But my personally favourite, for languages that support functions as first class citizens is to fully decouple the two objects via functions.
Such a solution, expressed as C# (as I'm more familiar with that language), might look like:
class Messenger : WebsocketHandler
{
private readonly Action<object> _messageReceived;
private readonly IEnumerable<WebsocketSession> _websocketSessions;
public Messenger(Action<object> messageReceived)
=> _messageReceived = messageReceived;
public void Receive(object payload) => _messageReceived(payload);
public void Send(Object payload)
{
foreach (var session in _websocketSessions)
{
session.Send(payload);
}
}
}
class Controller
{
private readonly Action<object> _send;
public Controller(Action<object> send) => _send = send;
public void MessageReceived(object payload)
{
// do something with payload
}
public void NotifyClients() => _send(null);
}
And then the code to couple the two objects at runtime might be something like:
void Setup()
{
Controller controller = null;
var messenger = new Messenger(MessageReceiver);
controller = new Controller(Sender);
void MessageReceiver(object payload) => controller.MessageReceived(payload);
void Sender(object payload) => messenger.Send(payload);
}
Java these days supports such an approach too. I do not know though how well this would work with IoC frameworks like Spring.
-
This sounds great but what about the fact
Sender
needs access to the session held inMessenger
. Would it make sense to extract them into some sort of session registry component?Robert Hunt– Robert Hunt08/09/2018 19:10:50Commented Aug 9, 2018 at 19:10 -
@RobertHunt Which one?Stop harming Monica– Stop harming Monica08/09/2018 20:23:22Commented Aug 9, 2018 at 20:23
-
@Goyo the
Messenger
class currently stores a set of websocket sessions as clients connect and disconnect, for theSender
to send a message it needs access to these sessions.Robert Hunt– Robert Hunt08/09/2018 21:35:43Commented Aug 9, 2018 at 21:35 -
1Yes, but knowledge of that session is not part of the Sender interface. Breaking Sender Method signatuure breaks the circular dependency between Messenger and Controller and abstracts away implementation details of sendini a message.Session is an implementation detail that happega to be necessart for Messenger that implementation the Sender. At some future point in time you might replace implementation of Sender with one that sends its messages to Kafka topic for example and Controller will not care a one bit.Roland Tepp– Roland Tepp08/10/2018 05:17:37Commented Aug 10, 2018 at 5:17
-
1@DavidArno Sorry, I had missed the fact you had
Messenger
implementing theSender
interface, it makes sense now.Robert Hunt– Robert Hunt08/10/2018 06:50:54Commented Aug 10, 2018 at 6:50
The Messenger component has a dependency on the Controller component to be able notify the controller of incoming messages.
OMG java does not have events!!!
It does have several alternatives though. For example
https://docs.oracle.com/javase/6/docs/api/java/util/Observable.html
Here you would make your Messenger implement Observable
and have the Controller call addObserver
The Messenger can then notify the Controller of incoming messages with its notifyObservers()
function without having to reference the Controller class.
There are several variations on the technique.
https://docs.oracle.com/javase/tutorial/uiswing/events/index.html
The Idea is that you pass in to the messenger the function to call when a message comes in rather than passing in the whole controller class.
-
This is a classic example of "when you have a circular dependency, often there is a third object wanting to come out". In this case, the object is the observer (which turns out to be the same object, but seen through a different protocol).Jörg W Mittag– Jörg W Mittag08/09/2018 15:52:37Commented Aug 9, 2018 at 15:52
In that particular case (when you need to notify someone about something), you can use an aggregator\messenger.
One of my favorite (from C# though) is this one: https://github.com/NimaAra/Easy.MessageHub
You create a message class, you subscribe your service, you fire the message from another service - that's it. Behind the scenes, aggregator just defines the list of subscribers, who should be notified for the particular message.
I'm pretty sure Java should have something similar.
Explore related questions
See similar questions with these tags.