Fixing Dependency Problem in a Complex Modularized iOS App

Does modularizing your app bring more headaches and more questions instead of answers to you?

Agung Pratama
7 min readJul 24, 2022
Photo by Dmitry Demidov: https://www.pexels.com/photo/brown-puzzle-pieces-3852577/

Let’s imagine a hypothetical situation. So, your team is growing and now has become bigger, your company now has multiple core business to runs. Which means now you have to prepare your codebase to handle all of them in the same app.

I might have picked a weird combination here and it may looks like a ridiculously exaggerated example to pick but please bear with me. Imagine the company that you are working now does: flight/ticketing business, food delivery business, car rental business, insurance business, P2P financing business, and telemedicine business. How would you design your codebase?

Modularization!

Each Major Features, Modularized.

You have decided to put things separately for each major features to be grouped inside of each Project Framework, with the goals of each business unit can be isolated and can be managed by its own codebase owner for each team. Skipping some headaches ahead, just for the sake of start modularizing while keep the codebase able to be compiled ASAP, you ended up in a diagram that looks similar to this.

Modularized App, first phase, over-simplified.

So it might work, now you successfully modularizing the app so each business features can be placed inside of each own Module framework respectively. Things are happily working as intended, until… some business feature wants to do something with other business features! It’s just make sense for each independent business that is grouped into one app to actually collaborate each other right?

Business units are not so isolated just like what you want your code to be after all. Business always collaborates.

Imagine that your product team has decide to make the user of your app that is looking to book a flight can automatically shows information of their user’s insurance in one of the booking flight screen, so they can do something like offering to open an insurance police if they don’t have any insurances linked.

Meanwhile, your telemedicine consultation feature also wants to have your food delivery’s team logistic to deliver the medicines after the consultation to the user.

Anything similar to those two examples above can happen anytime in an app. Depending on how big the intersection between those business features, you can have several options how to solve those requirements.

One of the easiest way to let a module to talk to other modules is: Import other business module frameworks inside the other business module framework.

So you decide to make some changes that might looks similar like this on the business framework layer.

Sample of how business units would work with each other

You managed to refactored out all of Logistic detail that resides on the FoodDelivery.framework to be used and imported by Telemedicine.framework and you put Insurance.framework to be imported by FlightBooking.framework

Viola! Now your app is ready to deploy with all of those requirements fulfilled.

Problems with this approach

While this can work, some of you might already have noticed the problems to this solution:

  1. It introduces verticality between the business frameworks. Now FlightBooking is tightly coupled to Insurance
  2. Insurance.framework now can’t import FlightBooking.framework if needed in the future due to it will cause circular dependencies problem
  3. Every time the iOS team do some changes in the lower level modules like Insurance.framework and Logistic.framework module, the incremental build in Xcode would try to rebuild their respective dependee modules like FlightBooking.framework or Telemedicine.framework and FoodDelivery.frameworks.
  4. The option of making mini demo app for each business modules are halted/not efficient since now you would need to build the entirety of your Insurance.framework just to make a FlightBookingDemo App

If you haven’t do any kind of modularization like this before, you might be thinking that these example of problems are exaggerated. Well you might be right, at least for your case. Every iOS team would have their own unique situations and problems, if you think that by introducing verticalities among the business framework is fine on your codebase is just fine, then it might be just works for you.

But, if you’re here because you’ve been in this kind of situation before and you would like to know what are the other options to make your modularization more loosely coupled and not being a “distributed monolith” here’s the juicy part.

Dependency Inversion Principle (DIP)

I’m not gonna put this article to be much longer by copy-paste-ing some kind of definition of DIP here, you can look it up on some other article or website. But the core usage of this technique is we would separate our Business modules into two framework layers: Implementation Detail Framework and Abstraction API Framework

Horizontal relationship between modules, even the previously low level modules like Payment, Promo, and Users. Oversimplified.

As you can see this diagram only draws the high level goals of how business frameworks has horizontal relationship between each other, added APILayer frameworks for each business modules and doesn’t show the details of how business modules that has requirements to talk to each other works.

The magic comes after you separates the abstraction layers to its own framework from the implementation detail framework. For the next step, to fulfill the previous business requirements, you can do something like this.

Example of using DIP from previous requirements

By this, Telemedicine.framework and FoodDelivery.framework are not importing Logistic.framework, but only implement the same abstraction of what Logistic.framework implements, the LogisticAPI.framework.

Meanwhile, the same happens on FlightBooking.framework that is now only importing InsuranceAPI.framework instead of the implementation detail of Insurance.framework.

What are the benefits of doing DIP on Module level?

  1. Modules are now loosely coupled between each others, meaning that now you can flexibly has other business feature’s capabilities like services or UIs accessed via protocols or abstractions without have to be tightly dependent to them. If somehow BookingFlight business wants to access Logistic business for whatever reason (you might want to provide ride hailing service to the airport for users that booked flight via your app) you are freely to import LogisticAPI.framework to BookingFlight.framework without any worry.
  2. Instead of depending on the whole module implementation (which could be slow to build) now the module would only needs some kind of module that conform the same abstraction that it depends on. Which means you can easily swap some kind of mocking to build a DemoApp that would be much faster to build (since the mock would be way smaller compared to the original detail implementation) to that particular business module.
  3. Incremental build time is faster when doing development locally, if you set your local development configuration to have Incremental build setting (you should). Like what the cons of doing tight coupling dependency by importing other module like the previous example without DI, you would have to compile the upper module when the lower modules are changed. But when set with DIP as long as the APILayer are still intact you are safe from the recompile hell since all of the heavy duty would be done and changed way more frequently on the detail implementation framework.
  4. No chance of cyclic dependencies when the business requirement are somehow reverted in the future. Since you would never access dependency on another detail implementation.

Some guidelines on how to separating Implementation Framework with its Abstraction Framework

  1. Make a protocol on objects or services that you want to expose to other layer and put it on the abstraction framework.
  2. You can import API frameworks freely to any implementation detail framework when necessary. But never import any kind implementation detail on each other.
  3. Keep the API Frameworks as small as possible and make it on the very end of the dependency graph. Anything that involves complexities on how things are done are most likely stays in the implementation detail framework.
  4. Use a Dependency Injection mechanism (or build your own! would love to write an article about this one too) to provide the actual objects needed from the composition root.

Closing

Using Dependency Inversion Principle to do your iOS App Modularization would make your modularization more loosely coupled and offer flexibility. Some level of modularization would work without it just be fine, and I know some people that would see it as an extra layer that is not necessary for their iOS codebase but in my opinion this is a technique that is worth knowing to bring up your iOS App modularization level even more.

You would never know when and how your business requirements will change in the future, so a loosely coupled codebase is always be a wise investment.

If you’d like to read the complimenting articles of this DIP modularization topic: Composition Root and Dependency Injection, consider to follow me on Medium so you won’t miss when it’s out.

Cheers!

--

--