Building a modular iOS app
At Grio, we’re often asked to improve our clients’ existing web and mobile apps — fixing problems, adding features, etc. — but many of our most interesting and exciting projects involve building a brand-new app entirely from scratch. I’ve had that opportunity on a recent project, which means that my colleagues and I have been thinking through some of the foundational decisions that can really only be made when you’re starting fresh. One of those decisions is monolithic vs. modular.
Monolithic vs. modular apps
A monolithic app is a single codebase in which everything — from networking code, to UI components, to login and presentation logic — works together to do a single job. Monolithic apps are relatively fast and easy to build, since you’re not spending much time worrying about how the different pieces interact and exchange information. As a result, they’re usually also cheaper for the client.
However, as you might imagine, monolithic apps tend to be messy. Because every component is interacting with every other component, small updates can have unintended consequences. If you want to make significant changes — for example, removing or replacing the shopping cart functionality in an ecommerce app — you’ll probably have to rebuild most of the app from the ground up. And if you want to reuse that shopping cart functionality in another app, you’ll have an equally tough job untangling it from the rest of the original app’s codebase.
A monolithic app: Quick to build, but a bit of a mess
A modular app, on the other hand, is made up of small pieces of code that fit together neatly but are largely independent of one another. Modular apps are well-organized and easy to test, reconfigure, and clone. Where the functionality of a monolithic app might seem “magical” and opaque (even to many developers!), modular apps are easy for everyone, including less-technical stakeholders, to understand.
A modular app: Harder to build, easier to understand
If given the choice, most clients will opt for a monolithic build — and that’s not necessarily the wrong decision. Short-term financial concerns just tend to outweigh longer-term considerations around flexibility and extensibility. In our case, however, we have a client who’s very invested in ongoing updates and additions, and they’ve afforded us the time and resources to go the modular route.
How a modular app works: Frameworks & design patterns
Modular iOS apps are constructed from frameworks, which include both kits and feature frameworks:
- Kits include (for example) UI kits and networking kits. These are very independent bundles of code, which means they don’t need to import any other frameworks or information — they simply provide an interface for the app to talk to a server, build views, etc.
- Feature frameworks include systems like login and authentication. These frameworks can also be independent in the sense that they’re modular, but they do need to import other frameworks and information to function.
To create frameworks and the interactions between them, we rely on design patterns. We’re currently working with three main design patterns for our login and authentication frameworks: the coordinator pattern, the delegation pattern, and the dependency injection pattern:
- The coordinator is like a puppet master that tells different pieces of the app what to do. There are many ways to build a good coordinator, but every coordinator needs to have three basic functions — it needs to be able to issue “start” and “stop” commands, it needs to be able to give directions to sub-coordinators (“children”), and it needs to receive and react to a “done” signal from a child coordinator.
- Delegation is a concept that we’re all reasonably familiar with — it’s the process of telling someone else to do some work on your behalf. In the context of a modular app, delegation simply means that one object can tell another object or variable (the “delegate”) that it’s performed some action, and the delegate can react accordingly.
- The dependency injection pattern gives the various pieces of code the things they need — such as data or frameworks — to make decisions and perform tasks. For example, our app injects the login framework with an object that includes dependencies like retry counts, a timeout interval, and a base URL.
Analogy for an app: A school music program
When I’m explaining the concepts above — kits, features, design patterns, etc. — to a non-technical stakeholder, I like to use the analogy of putting on a school music program. In this case, we have the following components:
- A school building — this is the app. It’s the larger structure in which students play instruments, parents applaud, teachers fret and hustle backstage, etc.
- A stage setup — this is a kit, specifically a UI kit. It includes lighting, props, setup, etc. With some fairly minor changes, the setup could be reused in any school, on any stage.
- A ticketing system — this is a feature framework, specifically a login framework. It requires more specific input (price tiers, etc.), but again, the basic framework can be reused across different performances and schools.
- Administrators and teachers — these are our coordinators. An administrator (such as the principal) might be our parent coordinator, directing the teachers (child coordinators), who in turn direct the student performers.
- Performances — this is where we see delegation in action. The performances (e.g., a choir group, a rock band, etc.) are all independent of one another. They tell the coordinators when they’ve finished performing, and the coordinators tell the next performance to start.
- A schedule — this is where dependency injection enters into the picture. Let’s say our principal wants to get home at a certain time, so he’s checking the clock after each performance and passing information to the teachers (his child coordinators). The child coordinators wait for that information (are we on schedule, or behind?) before giving the go-ahead for the next performance.
App-as-music program: Frameworks, coordinators, delegation, and dependencies
In addition to providing a good reference point for the structure of a modular app, this analogy nicely illustrates the benefits of modular development. A school that keeps the stage setup separate from the ticketing, and that has an organized system for passing instructions to independent groups of performers, will be able to put on the same show year after year and even in different locations. With a monolithic approach, on the other hand, they’ll be in trouble the minute the venue changes, the assistant cancels, or someone gets sick — which, as anyone who’s participated in a school music performance knows, is pretty much guaranteed to happen.