Jacob’s Tech Tavern

Jacob’s Tech Tavern

Designing The Perfect Modular Architecture

Applying everything I've learned across 10 years of building

Jacob Bartlett's avatar
Jacob Bartlett
Jul 29, 2026
∙ Paid

I’ve intentionally avoided treading the worn-out arguments about MVC, MVVM, MV, VIPER, and your favourite homegrown solution. I don’t consider it a gap in my blog content, because…

  1. They aren’t very interesting to write about.

  2. I don’t consider this proper “architecture”.

Your choice of screen-level file layout is just about consistency. Orienting yourself in an unfamiliar part of the codebase. Pick one, and stick to it. That’d be the whole blog post.

Proper Architecture™ is about the building blocks of our system, and how they fit together. It’s as much art as science. Draw boundaries and boxes across your codebase, and gerrymander until your dependency graph sparks maximal joy.

Today, we’re going down my long-awaited hit-list:

  • The what and why of modular iOS architecture.

  • Getting on the same page vis-a-vis dependency graphs.

  • The good, the bad, and the ugly architectures I’ve experienced in 10 years.

  • How I utilised this experience to design Granola’s modular architecture.

By the end, I hope to transfer some of my architectural instincts to you.


Contents

  • What is Modular iOS Architecture?

    • Build time

    • Code quality

    • Organisation problems

    • Modular solutions

    • Tooling and Libraries

  • The Dependency Graph

  • Modular Architectures I’ve Seen

    • Single Module

    • Core Module Architecture

    • Layered Modular Architecture

    • Naïve Feature Modules

    • The problems with feature modules

    • Service & Orchestration Layers

    • Feature Modules with API Contracts

    • Hyper-modular Architecture

  • How I Designed Granola’s Architecture

    • A POC with big dreams

    • Architectural Pushback

    • This is My Design

    • The Big Refactor

  • Last Orders


This post is so full of knowledge, that your email client will cut it off. Read on my website for the best experience.

Read on my website


What is Modular iOS Architecture?

Many great iOS codebases begin as a big ball of stuff. One app, one target, one module, one project. Everything lives in one place, and making a new feature, service, or model is as simple as writing a file into a folder.

This works surprisingly well for longer than you’d think, but then you might start to feel the codebase pushing back on you.


Build time

As a monolithic module grows, small changes might invalidate a larger portion of the dependency graph (uh, dependency blob?). Incremental builds become… less incremental, code completion inevitably sloooows down, and Xcode struggles to cache reusable build outputs.

Ask me about the Build Time Assistant in Xcode. Step 1 (highlighted): Compute dependency graph.

Code quality

With one module, you often cannot enforce meaningful boundaries at interfaces, because internal code (the default access control) is visible everywhere within that module. You need discipline and lots of private methods to avoid spaghettification.

You know what I mean if you’ve ever shamefully tasted that sweetest forbidden fruit: turning that private function internal just so the unit test can speak directly to it.

It’s also mildly cruel to deprive junior developers of their first change to legitimately use the public keyword. Or better, public private(set). You feel so badass the first time.


Organisational problems

Often an architecture grows to reflect the shape of the organisation maintaining it. Companies with a platform team will often have a big Core module. “Feature teams” will have modules split up along pod lines.

When teams, pods, tribes, scrums, etc, are divided by feature ownership, but the codebase has no corresponding boundaries, it gets muddy fast. Who owns those services, that model, or this infra? Engineers may step on each other’s toes, re-implement solutions twice, or design wholly different APIs.


Modular solutions

Dividing your app into modules gives your iOS app clearer boundaries between features and services, making the codebase easier to understand and explain. It allows you to enforce encapsulation, encourages reuse, and draws straightforward ownership boundaries across modules so tons of devs (or agents?) can work in parallel.

Modularity improves build times (usually!!*) by limiting how much code has to be recompiled after a change. If you edit one top-level feature module, Xcode can reuse its cached build outputs for non-dependent modules, compile independent modules in parallel, and cache more granular outputs.

*There can be too much of a good thing. Make your dependency graph too complex and interconnected with many tiny, poorly-bounded modules, or create loooong, deeply-layered chains of dependencies, and you’ll just slow things down instead.

This is actually a quite nice dependency graph, redacted to protect the innocent

We waited an appropriately long time to refactor Granola: I’ll go into more detail soon, but our ultimate modular redesign was motivated by a combination of 1) team growth and 2) some very specific pushback we were experiencing from the codebase.


Tooling and Libraries

When you want to modularise your app, the default is SPM. It used to be bad. It’s good. I like Tuist. Rolling raw Xcode projects are probably fine if you get Claude to manage the boilerplate. CocoaPods and Carthage are mostly-deprecated, you maniac. Don’t touch Buck or Bazel unless you are willing to open that particular Pandora’s box, and maintain it forever.

When you create a module, you need to choose whether you’ll use a static, dynamic, or mergeable library. These are incredibly important for modularisation, but very out of scope for this article. To make a long story short, pick between them to balance build speed, bundle size, and launch time. To make a long story long, you can read this classic of mine:

Static, Dynamic, Mergeable, oh, my!

Jacob Bartlett
·
November 18, 2024
Static, Dynamic, Mergeable, oh, my!

If you want to embarrass a senior iOS engineer, ask them to explain the difference between Dynamic Frameworks and Static Libraries.

Read full story

The Dependency Graph

One important concept in modularising an app: the dependency graph. Consider the modules as boxes or nodes along a graph. Yes, the data structure (you thought LeetCode was a waste of time?!). One node can’t compile unless all its dependencies, or imports, are compiled.

Your app builds the leaf nodes at the bottom of the graph, then modules depending on them, then modules depending on them, recursively, all the way up to your top level app module that ties everything together. While it can parallelise a lot of this, you can’t build a node without its dependencies. You get it.

This is why spaghetti architectures can slow down your builds: one tiny change can invalidate the dependency graph of dozens or hundreds of modules importing it across your graph.

Some of the architectures we’re looking at today are optimised with the Xcode Build system in mind. This is actually the core insight behind Tuist’s TMA and “Feature Interface” API contracts:

If your module’s public interface stays the same, modules depending on you will often not need recompilation, even if your module changes internally.


Modular Architectures I’ve Seen

I first wrote about modular architecture 2.5 years ago, somewhat early in my blogging career. For a while, I was embarrassed to share it, because I evangelised an architecture that had some serious scaling issues. Amateur hour. I’m more nuanced these days. Simple can be better.

Let’s take a look at some of the architectures I’ve experienced over 10 years of engineering, and dozens of projects.

But first, a word from my sponsor. Erm, from me:

Keep reading to learn about single-module, core, layered, and naïve feature module architecture, and the serious problems hidden within.

Next, I explain the more advanced approaches I’ve learned since starting by blog: service & orchestration layers, API contracts, and hyper-modular architecture.

Finally, I’ll demonstrate how I took all these learnings to design the perfect architecture for Granola’s iOS app.

Get the article now, plus:

⚓️ Access my full library of 50+ paywalled articles
🚀
Read free articles a month before anyone else
🧵 Master concurrency with my full course and advanced training
🧑‍🚀 Get a free copy of my new eBook, “Land your iOS Tech Job”
❤️‍🩹 Support independent, sometimes funny, tech writing

Get 14 day free trial

User's avatar

Continue reading this post for free, courtesy of Jacob Bartlett.

Or purchase a paid subscription.
© 2026 Jacob Bartlett · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture