By Jonas Liao on December 22, 2017
At Hootsuite, our value proposition lies in the ease of managing multiple social media accounts in one central place. This means that we have to build a platform to integrate the numerous social media that we support. As part of my team’s effort in social channel optimization, we found 38+ hard coded places across various large codebases that we had to modify to support a brand new social network.
MotivationTo improve the development process as we seek to add the next N social networks, we want to find an efficient and elegant way to support new channel integration. The 38+ hard coded places we had to modify greatly hinders our efficiency and is a tech debt we seek to tackle in order to effectively manage the supported social networks. Having to add an extra hard coded checks like ‘case Twitter, case Facebook’ is a painful and long process that’s cumbersome, therefore our team decides these problems can be solved with a few solutions including generic networks configuration that’s accessible by all the relevant components that need to handle the social network, and using a few methods to break down a monolithic code base.
Overview of the problems
Monolithic CodeTo give a brief overview of how the monolith looks like without showing the actual code, consider this: there’re 514,697 lines of code (as of Dec 2017) in the platform and 120,449 LoC in the social network management service.
Hard CodingAs of 2017, the main channels that Hootsuite supports include Twitter, Facebook, LinkedIn, Google, etc. To give an example of how we determine whether the code handles a social network right now, we have something like:
Codes like these are intended to ensure that the correct social network is selected. There’s a caveat though, giant social networks often have other smaller offerings like Facebook Group, Facebook Page, and other products they produce. The current solution is to have a plethora of if statements to make sure that the target social network product is properly caught in the if statements. Extensive defensive coding like these can be a concern if the data structure is edited in the future, or when it misses a subtle edge case that has never occurred in the past, which is a hard problem on its own.
To illustrate a potential problem when the data structure changes, consider this example code
One problem arises if in the future, the networks’ prop, or object, is modified to not have a key of socialProfile or type, this code will break and if you’re lucky, you get an undefined error.
On Decomposing the MonolithMy team is working on adding a brand new social media. We firmly believe in letting the microservices/components decide whether they can handle a specific data. This way, we avoid doing extensive ‘if’ checks, and in the case that the component cannot deal with the input, do nothing.
To achieve that, we relied heavily on the delegation pattern. It is an object composition technique that allows a second object to handle the request.
We employed this pattern to allow a parent class or object to delegate the responsibility of handling the request to a child object. This way we hide the complexity and allow the subclasses to decide whether they understand the request. This is important as Hootsuite has a large number of services that are interconnected and the ability to delegate the corresponding child classes or services that can take care of the relevant requests lets us achieve code reuse. Therefore, this pattern a monolithic code base can effectively delegate its requests to other microservices, reducing the size of the monolith.
On Hard CodingAs mentioned above, one major pain point our team encounters is to know where to insert an additional ‘if’ statement when we add a new social network. For instance, if we were to add TheNextCoolSN, we would have to do
To avoid this, we created a networks configuration file. In this file there are network specific details that are different for every social networks. For example, for Twitter, the word limit you can put in a tweet is different from that of Facebook. We categorize this effort as our goal to achieve data driven development where we allow the data to decide the flow of our program.
The networks configuration file passes its data down to a parent class that determines which child classes to call based on the data that’s given to itself.
We can define a network specific structure such as
Using the same technique, we can define a network specific structure as properties that should be passed into the component easily, and allow the child class to get its correct properties or parameters. This allows the data that’s passed in to determine which child classes should be rendered, effectively eliminating the need for if statements.
There are tradeoffs to be made with this programming approach though. The code is less readable due to the abstraction created by using data, and the code can suffer run time errors.
On General OptimizationThere is another problem my team wants to solve. We realized that due to the many social networks we currently support, there are many network specific message models that cannot be reused with other networks. Therefore, we decided to build our generic model that will support all future social networks.
There are drawbacks to this model though, including the lack of type safety and the incompatibility with Google’s protocol buffer. Protocol buffer is used extensively at Hootsuite due to its lightweight and versatility, however, they do not support generic data structure such as the one we developed for our generic social network object.
We decided that the tradeoffs between ‘genericity’ and code reuse outweigh the drawback.