Android multi-module architecture

Jules Tréhorel
iAdvize Engineering
5 min readAug 3, 2016

iAdvize Android journey started less than a year ago. At first things were pretty simple, 1 app, 1 developer, 1 repository, but as every development team, it started growing (now 2 apps, 3 developers, 23 repositories).

To help our day-to-day work on our projects we started looking at the microservices architecture that iAdvize, as a SaaS company, already applies on its web platform and decided to follow a similar approach.

The Cut

We split-up our app into 4 kinds of modules:

  • library (non-core modules)
  • service (core non-UI modules)
  • UI (core UI-module)
  • app (core orchestration of modules)

The Good

I must admit that it was quite satisfying to see this big monolith being piece by piece cut into independent little modules with specific missions, and so refreshing to see our entry-point app orchestrating those modules to recreate the exact same final user experience we had before.

Each module is hosted in its own git repository (managed with git-flow), has its own build script that publishes its output to Artifactory, which is then used in the upper modules.

This allowed us to create our second Android app in a jiffy as it shared a lot of common modules with the first one. This also means that any bug we fix in a module benefits all our apps.

The Bad and the Ugly

However it also destroyed our velocity as we under-evaluated some of the impacts:

  • Changing one line in a child module needs a build to be tested into the parent module.
  • Building a version of the top-level app (sprint objective) needs a sequential build of every module.

Few weeks after the module switch, we find ourselves spending more time waiting for our build system to generate our artifacts than to actually develop or test our work. A new version of the app would take half a day (for a simple 7Mo application) as we use a limited number of build machines on CircleCI.

It seemed that the micro-services architecture so popular for web apps was a pain to work with for the mobile world.

This is our app! Wait, what?

The Stitching

Well obviously our move was either dumb or too minimalist. As we had multiple applications, it was not acceptable to go back to a monolith architecture, so we brainstormed about actions to take so that we could work effectively.

We started documenting about mono-vs-many repositories, the multiple ways of using multiple modules into a single project, especially git-submodule & git-subtree.

While we are still defining the right architecture we would like to work with, here are the few steps we took to improve.

Samples

This was the first, quickest and most obvious solution to put in place. Each module (well, almost) comes with a sample test project that allows to test direct modifications in a restricted and highly customized application.

This is particularly useful for UI modifications.

Automatic release

While doing our end-of-sprint release of the application, we quickly understood that we were doing a lot of manual tasks with absolutely no value:

  • Create a git-flow release
  • Update the dependencies to latest available
  • Update build number
  • Commit, finish git-flow release, push

This could be easily wrapped into a custom release script allowing us to release a module in a single call. On this release process our next step would be to implement a chain build, as for now, we have to wait for a module build to be finished to manually release the next one (it was an easy task to do on Jenkins but I could not find it on CircleCI yet…)

A typical end of sprint release.

Local environment

As a previous user of git submodules, I was not really eager to go back to that strategy. It is sometimes long to explain Git to a new developer that has never used it, submodules add a bit of a mess…
“Well, you are in your submodule but not in a branch, you are in detached head, so checkout your branch first before committing, then you can commit in your parent to update the SHA1, not the branch.” Add git-flow to the mix and you are done with submodules.

Moreover the tool we needed was more complex than just linking a project to its modules. Our current architecture (external hosted modules) was the one we intended to have for a release environment, our problem was just for our day-to-day local development environment. By digging a little bit we found a tool called Repo (used by Google for Android codebase).

Repo helps you downloading projects from multiple locations into a custom defined directory architecture thanks to one simple xml config file. It allowed us, from the main application git repository, to download all modules into a specific directory, and synchronize them to the development branch.

On top of that we built another custom script (we called Repoid) which goal was to set up an environment fitted for local development as well as release testing. Basically it consists in using Repo to download all modules and synchronize them, modifying every gradle build file that depends on remote artifacts, so that they point to the locally downloaded modules (and revert everything when you want to go back to remote architecture).

Without compromising with our separated multi-module strategy, and with just a little bit of scripting, we are now able to:

  • Test local modifications from a module into the full top-level application.
  • Integrate changes from other merged branches directly in our local environment (quicker integration tests).

What about you?

Do you work with similar multi-module Android applications?
What are you practices (good or bad) on the subject?

--

--