Make a Bridge for your message

Aleksei Pichukov
The Startup
Published in
9 min readOct 18, 2019

Making complex applications, you will most likely encounter the problem of transferring messages between objects that are not directly connected.

I’m a huge fan of examples, so let’s say that you have an iPad app that uses something like the Master-Detail controller to represent the Main screen with User status and some menu that you can show from the Main screen on the Detail view part. Yes I know it is not a good UX, but that’s exactly what your Product Owner and his best UX Designer want to see and they don’t want to listen to any concerns from your side (sorry for it, but it’s life). The status you get from the Backend part of your service using some REST API. You have to implement a new feature that your PO just came up with. You need to change the status of the user. This should be done from a child screen of Settings that is the child screen for a Menu. The status changes randomly based on some logic on the backend, so from the UI/UX perspective, it’s just one button to do it.

After a talk with your PO, you go to the restroom, cry for half an hour there and start to think about the architecture. You already have all screens implemented, doesn’t really matter what architecture you use, it can be VIPER, MVVM, MVP, MVC, you are a good developer and try to keep SOLID principles but you also like to keep things simple and not over complexing stuff, so you found some balance in architecture. The only thing you need to do is to send the request to your server that the status has changed, after this your main screen will take the new status from the server. But you also need to notify the Main screen that it should update the status.

You like to inject dependencies and it looks like exactly the place for it, so you make some protocol to inject the object that responsible for the Main screen update: MainInformationMutable (I’m really bad in namings) that can look like this:

Main Screen + APIProvider

And of course you need to inject you APIProvider object that confirms APIProvidable protocol.

Looks good, only two dependencies, but your PO came to your place and ask also to implement some Blink effect for the whole screen after the status has changed and also add some record to your internal Logger about it. He thinks it’s a great idea to show the user that he did an important action. After another half an hour crying in the restroom, you add the protocol for Application effects and for Logger:

Blink + Logger

Ohh, now it’s already four dependencies to inject (IMPORTANT NOTE: this is just an abstract representation of components in the app, if you use some Routers, Coordinators, Injectors or any other hight level objects to handle the transitions in your app and DI, you will have something similar but encapsulated there).

And we have no idea what happens in POs head right now. And also you understand that with your current architecture you will need to inject all these dependencies throw the whole chain of objects that lead to your Change Status screen component. Looks a bit complex for our simple task isn’t it? Let’s think about what can we do to reduce the complexity. You know that it’s a good solution, you don’t have global objects, singletons, you injected everything you need but for unit testing, for example, you will need to mock all your dependencies and every time your PO will come with a new amazing idea you will have to change the code inside the component that actually shouldn’t be really responsible for it.

Let’s think about it from a logic perspective. You have a component with the only function to handle the button tap from the user and initiate some process based on it. But you also put the responsibility for starting the Blink, update the Main screen, make the network call and also add a new record to the Logger. It’s an infinite discussion of should we put it there or not because from another side you just trigger something using abstractions injected, so it’s just a message sending, but again your module knows about all these objects exist in the app and why should it care about it. I honestly don’t think there is a correct universal answer for it, I think we need to keep the balance in the app between everything that can affect it. Here one of the biggest factors that affect the app is our PO with unexpectable ideas and limited time for implement it, so in your case it really makes sense to remove this functionality of Blinking, Loggin and Updating the UI from our component but you prefer to keep the Network call logic here just to make it simple for handling the async operation because anyway it’s the most important operation from the business logic perspective (we also will discuss the options of avoiding it). In the end, you decided to have only APIProvidable injected to your module and some new mechanism to tell the other components involved in the process to do something after status been changed.

Let’s think about the pros and cons we have in that case:

Pros:

  • It reduces a complexity
  • Out component becomes more independent, now it doesn’t know anything about other components in the app (except the APIProvider)
  • As a result of the previous point we don’t need to change the code in the component and Unit tests cover it in case of any new idea to change something else in the app will come to the head of our PO

Cons:

  • We start to lose the app flow

We don’t have many cons :) But I think the one we have is really important to discuss. Again, there is no black and white side here. I think that to see the app flow when you can go step by step from component to component and see the sequence of actions is very important for readability and error handling. But here it’s just a compromise we ready to take to have more independent components in our app.

Now the flow for our component is simple. We call the changeStatus method from our injected provider and in the completion block send some message to notify other components that status has changed and they can do whatever they need. The question is how and where will we send this message. It’s a lot of options we can use for it, for example, we can use the Observer-Observable pattern. In that case, our Presenter (or some other object depends on the architecture you use) should be Observable and contain the list of Observers subscribed to it for the update. Looks good but how will we implement this subscription process? We can inject the list of all subscribers that would have some common interface. It can be an option, but in that case, we will need to keep all this dependency injection chain or use some high-level object to keep the structure of what subscribers should be injected to each observable, looks like we don’t reduce the complexity with this approach.

What else can we do? The simple solution is to use some global object for keeping references for all these observers in one single place and just send the notification using it. The observer that subscribed to the sender will handle the notification and execute some action with respect. It looks like a simple approach, we can make our own notification center or use NotificationCenter that platform provides for us.

But there is one big issue here and this issue is a GLOBAL word. We don’t really want to use a global object, but what else can we do?

Let’s take a look at any application structure we can imagine. We can say that the app is an undirected graph with our objects as Nodes there and connections between them like a Bridges. It’s something like Bridge Net of the city. So basically we see that the message from any place of the app can be transferred to any other place just using current connections (Bridges) that objects (Nodes) have without any Global object.

Let’s try to think about how could we implement it.

Each Node in the graph should to have some unique ID but also we want to send messages without knowing any ID, we just want to tell all objects related to Status change do something, so let’s also add the Type for all Nodes in our graph. As a result, we have a Node with a unique ID and Type. We also can make a list of Types in the node that represents the list of notification types this Node can handle. But just to make it simple here will use one single Type for it.

The next step is to think about the algorithm of sending the message. One of the options is to use Dijkstra’s algorithm to calculate the fastest path from one Node to the other and send the message using only nodes on the middle, but in that case we will have to keep the information about the graph structure and paths somewhere, so one more dependency to inject and a global object that we want to avoid. We can also create a message that will contain the information about the sender and receiver and just send this message to neighbor nodes recursively before the message will be delivered. Looks like a good option for us, but it has one issue:

  • The message should have some mechanism to be removed in the node where it has already been

To solve this issue our Message can look like this:

BridgeMessage

Also, we have a list of visited Nodes and the DispatchQueue to make thread-safety access to it.

We have just two methods here: add the new Node to list and get the information of have we ever been in the Node or not.

Great, now we have a message, let’s move to the Node itself. It should have only two public methods:

  • add the new Node to the list of connected nodes
  • send the message

I will not put the code of the Node here, you can find it on GitHub. The main idea is simple, when we get the message from any other node, we need to check that it’s a first time we are getting this message (if not, just removing it), that the message is not for us (if it is, just handling it) and send it to all connected nodes. That’s it.

In the end we have all our objects connected with some BridgeNet system that we can use for sending messages, nothing is coupled, we just add one more common protocol to confirm and one object that represents the Node itself, but for any new functionality we don’t need to change any other part of our component, we don’t need to change UnitTests, create new Mock objects and we don’t have any global object that handles some app state or any other information that we could use from all components in our app.

But we still have the APIProvider injected in our component, so we can also remove it and send some special message that will have the other message inside that should be called after the first operation finished and it will look like this:

but how to do it we can discuss next time.

P.S. I don’t want to tell you that this is the good way to solve the problem, I just think that there are no black and white answers in the Architecture and better to have more options you can use than to have less.

GitHub link to Pod with simple implementation of BridgeNet

--

--