Moving towards a Micro-Service Mindset on Android

Most Android applications are built as monoliths.
In software engineering, a monolithic application describes a single-tiered software application in which the user interface and data access code are combined into a single program from a single platform. A monolithic application is self-contained, and independent from other computing applications. (Source)
As a software engineer, you are most likely to work on different projects. If you work on a small or medium scale project, you will probably have a single module. If you work on a larger project, you most likely have more than one module in your project. You either have modules defining the vertical layers (like api
, data
, domain
, ui
) or you took a more horizontal approach and have feature modules. You may or may not have a certain set of core modules on which your other modules depend, but you most likely only have a single app module. Usually there is a single APK — a single entry point through an icon displayed in the launcher, and looking under the hood we can spot an app
module that houses this entry point and connects the dots. For most applications, this works out just fine. If you create a new project with Android Studio it also creates an app
module for you to host your application’s code-base, pointing you towards this direction.
While a monolithic architecture is not necessarily a bad thing, in this post we are going to take a look on why some applications should move away from a monolithic structure, how this can be achieved, and both advantages and disadvantages of these approaches.
Different Perspectives
Software engineers have been trying to move away from monoliths for a couple of years now. The most visible migration away from a monolithic architecture happens in backend with the move towards micro-services. Micro-services operate with limited responsiblities and a fixed scope. Between these services communication happens through well-defined interfaces over implementation-agnostic transfer layers (for example HTTP requests or WebSockets).
There are multiple advantages of a service-oriented architecture. To keep it short, here’s a list of a couple of them:
- Scalability
- Agility
- Fault Tolerance
- Software Stack Agnostic
- Clear Separation of Business Concerns
- Faster Development
- Faster Deployment
Of course monolithic architectures also have their advantages — it’s not all “bad”:
- Single Deployment Unit
- All Code in one Place (which is nice for humans and IDEs)
- Majority of Apps built like that (a lot of resources, tutorials, …)
Taking a first look at these lists, they seem to apply mostly to backend systems. Mobile applications are built “completely different”, so why should any of this apply to mobile development? Actually, most of the above listed benefits translate to mobile pretty well. Service-oriented architectures are well suited for growing engineering teams and a growing feature count.
Just taking a look at Android in the last couple of years the most obvious changes are the technologies used:
- Java / Kotlin
- Design Pattern (MVC, MVP, MVVM, MVi, …)
- Multi-Threading (AsyncTask, Loader, RxJava, Coroutines, …)
- Database (raw SQLite, GreenDAO, Room, …)
- Android X (Architecture components, Navigation, …)
The Android eco-system is moving rapidly and is not afraid to pivot. A monolithic architecture forces the engineers to agree on a certain tech stack — due to the dependency. In a service-oriented architecture, each service can be flexible. This can of course be seen as both an advantage and disadvantage. On the one hand it doesn’t force engineers to use the (maybe deprecated) tech stacks of their predecessors, on the other hand it can lead to very different engineering approaches being used. But with communication within the team, this should be an easy problem to solve.
Monolith and Modularization on Android
An Android application built as a monolith can be identified by having an app module on the top of the dependency chain. If your project has vertical layers as mentioned earlier, it’s a monolith. If it’s built with horizontal layers (feature-modules), and the app module on top has a dependency on all feature modules, it’s a monolith. Most of the applications will have one of these approaches. It’s the easiest way to set up your application, it allows for a straight-forward navigation pattern througout the application (as all parts of the app are known due to the dependency chain), and it makes sense from a conventional point of view.

A non-monolithic Android application would look a bit different. There is no longer an app module on the top of the chain. There is no module that has god-like knowledge about all features, instead features are built in an independent way. The app is built on a need-to-know basis — which makes a lot of sense. Every feature module (or feature module-tree) only knows what it actually requires for its functionality.

Since the application will still run in a single process on the device, there is still the need to connect the dots and to combine all of these modules into an actual application. Features need to be able to navigate between each other, data has to be transferred between them, and backstacks have to be provided to ensure proper navigation throughout the process lifecycle (and beyond that, once the process has been killed and the app is recreated).
Splitting the application into feature satellites working on a need-to-know basis, one of the things we still have to think about is navigation. In a monolith navigating between features is pretty simple due to classes and features being known at compile-time. In a service-oriented architecture, this is not given, thus navigating by classes doesn’t work. An easy solution would be to rely on fully qualified class names (com.brainasaservice.app.feature_service.MyActivity
) and rely on reflection — but that feels a bit dirty.
Thinking back to microservices, an implementation-agnostic way to navigate would be the way to go: Deep Linking. Just recently, I have published a post with more detailled and thorough thoughts on why deep linking is a good candidate for navigation in modular applications.
Deeplinks are supported natively by the system. They require no tight coupling between components, as feature satellites / services can simply register themselves as processors for certain schemes. Deep Links are capable of transmitting data, there is a well-defined scheme.
With the advancement of Android X and the architecture components, we also get access to the navigation component, which coincidentally also supports deep linking out of the box.
Feature Satellites
In a service-oriented architecture, each feature acts as a (micro-) service. Each of these services exposes its set of capabilties, making itself discoverable.
For example in a car-sharing application, you’ll have a service-module that enables Bluetooth communication, so the service exposes itself with a capability to communicate with certain vehicles through Bluetooth. Devices that don’t have a Bluetooth chip don’t need to load the Bluetooth communication service — with a service-oriented architecture, they simply don’t.
In a video-streaming application you might have a couple of different video players, each with different sets of capabilities. One of them consumes MP4 files, another one can consume HLS streams. Again, each of these services exposes its capabilities and they can be loaded on-demand.
Going back to the beginnings, our app module would act as a service registry. Each feature registers themselves as available (with their certain set of capabilities) once they are loaded. The app module serves as a broker that manages the application’s process, but delegates the actual UI and/or functionality to the feature satellites. It also handles the on-demand processing and downloading of feature satellites.
I published a repository on GitHub that shows an example of a service-oriented architecture. Feature modules are treated as services that provide a configuration and a certain set of capabilities to the application, instead of being referenced directly through the app module.
Keep in mind that it’s very basic — it provides a sample of three different service
modules that provide different services — ranging from providing UI elements, hosting their own Activity
to simply offering image processing. Each service injects its own configuration into a registry hosted by the app module, thus making it available.
The service modules thus offer a limited API or set of entry points (the capabilities) to the application, leaving the rest as implementation detail.
Google’s Dynamic Delivery
The Google Play Store recently started to point developers into a service-oriented direction. Starting with App Bundles — Google’s approach to separating resources — Google now presented Dynamic Delivery — enabling developers to download service / feature modules during runtime.
With dynamic delivery, modules are declared as onDemand
. This feature is available starting with Android 5.0 (API 21). If an application supports earlier versions of the OS, a compatibility setting (fusing) can be used to include or exclude the feature for these builds.
To Be Continued
With more diverse code styles becoming possible in the Android ecosystem, it becomes more important to add modularization to a certain degree to applications. Companies at scale — for example Instagram — have been doing it since a couple of years already and heavily rely on hot loading feature modules during runtime (they also open-sourced the lazy module loader on GitHub). Now, Google offers its own solution to tackle the problem and to allow applications to be built for billions of users.
Even though Google supplies us with a way how to load features on-demand, it is still a topic of discussion on how to properly include these features into the codebase.
If you already have some experiences with loading features on-demand, hot loading code — with Google’s dynamic delivery feature or maybe even with Instagram’s Lazy Module Loader let us know in the comments. If you made experiences with feature discoverability, navigation patterns or in general — feel free to share.
If you liked this post, want to discuss it or simply stay in the loop — follow me on twitter: