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(...);
}
}
Best Answer
The simple solution to this is to recognise that
Controller
doesn't need a dependency onMessenger
, it needs a dependency onsend
. So provide it with that dependency:Now,
Controller
just has a dependency on theSender
interface, removing that circular dependency.UPDATE
As the OP points out, the above removes the direct circular dependency between
Controller
andMessenger
. However, if constructor injection is used, the dependency still exists at the point of creation of the objects: we need to createSender
first to satisfyController
's need for aSender
, but we need to createController
first asMessenger
needs to callcontroller.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:
And then the code to couple the two objects at runtime might be something like:
Java these days supports such an approach too. I do not know though how well this would work with IoC frameworks like Spring.