This blog post discusses our case study of mobile app to Server communication and how we applied S.O.L.I.D patterns to the app with subsequent refactoring(s). We initially started our Server as a hybrid of Backend-As-A-Service (BAAS) and in-house implementation. Later we moved completely in-house. Throughout we accessed Server using BAAS SDK and our REST API with JSON as the data exchange format. The following are the refactoring(s) applied to the iOS app though the same is applicable to Android as well.
What we had
This design was rigid because most classes have direct access to concrete classes. Small changes effects too many parts. And we cannot swap a new Server engine without touching the whole project!
And in essence it clearly violates Dependency Inversion Principle (DIP) because higher level modules are depending on lower lever modules rather than on abstract interfaces. More importantly most of Server business logic is duplicated causing any improvements and bug fixes a nightmare.
So we introduced ServerManager that confirms to a ServerInterface. This module is responsible for all server logic and communication. All app components should use this module to communicate with Server. This confirms to DIP since high-level and low-level modules depend on well defined abstractions.
But it has a problem. It violates Single Responsibility Principle (SRP). The Server Logic Implementation is a monolithic module of server specific business logic and core HTTP network communication to perform this logic. Problem is – the business logic is directly using a specific networking library. This makes swapping core networking library (like NSURLSession to AFNetworking) not an enjoyable task. Also, the borders are not testable. We are aware of this and let it live since we are planning for a complete revamp of our backend in couple of months.
As part of this refactoring we focused on separation of concerns – business logic should be agnostic of core networking. So we introduced HttpClient module. This module’s sole reason of existence is to serve HTTP requests by abstracting the kind of network library used to perform this task. This module is specific to communicate with our backend using REST and JSON. It provides a defined interface of HTTP operations of POST, PUT, GET and DELETE (in addition to client specific authorisation and priority queues). The concrete classes takes care of the implementation. In addition to confirming to SRP this refactoring helped us to swap network libraries and confidently test the borders. More importantly we are able to share the HttpClient module across our sister apps which also communicate with our backend. As we tested and matured this module the results are usable across the board.
Introducing more interfaces and modules is good sign since abstractness increases with stability and maturity of the system. Fews things to note:
- Design maturity is an iterative process. It gets better with iterations. Trying to achieve a mature system design in the first-cut is not pragmatic.
- Designing a testable system is essential for a core component like Server communication. This makes maintaining and running tests easy on every iteration.
- Adhering to the design principles is a matter of discipline. In other words – it is a collective understanding among developers to maintain the system integrity by adhering to the agreed principles irrespective of project pressures.
We did apply the same design to Android app. At one point we planned to shift from good old loopj to Square’s OkHttp. The change was so manageable that we provided OkHttp implementation and ran tests – all within lunch to end-of-the-day!