Scaling Mobile Development at Microsoft — Mental Model

Anand Nath
Microsoft Mobile Engineering
10 min readJun 12, 2024

In the Scaling Teams Mobile Development series we looked at scaling development in a single mobile code base. Since then, we were at work, looking at up-levelling this problem statement and look at how to scale across mobile applications. Here is a new series which tries to throw some light around this topic. This article is the first of a series, starting with understanding the problem statement and a high level solution. In the following articles we will dive deeper with an example and discuss practical aspects of how we got it done. Let’s dive in!

Photo by Hans-Peter Gauster on Unsplash

Shared experiences

Microsoft has a suite of mobile applications. Each of these applications are developed in separate code base, has its own release cycles. There are a lot of overlapping experiences across these applications. These experiences are also implemented, tested and maintained independently in each application.

Here are some examples:

File picking experience across Microsoft mobile apps.
Calendar experience across Microsoft mobile apps.
File sharing experience across Microsoft mobile apps.

In order to drive consistent user experience across these applications, Microsoft is invested in developing “High Value Controls” aka HVCs. For example, there is One Camera which provides camera functionality for most of the Microsoft apps, there is One Player which provides video playback in Microsoft apps and some more.

Most of the HVCs are developed in silos without any common considerations or guidelines. Some HVCs are developed inside one of the Microsoft mobile apps and then lifted out to be positioned as reusable. These come with the remnants of the app specific concepts and logic, which does not sit well with other apps. This kind of HVC development lack consistency, coherence and interoperability. This also leads to a series of issues for both the Microsoft mobile apps and HVCs.

Issues for Microsoft mobile apps

Note: In this article, we will use Android as the chosen technology to explain the solution. Same thought process applies to iOS as well.

Current state of HVC integration

There are 3 main issues that we came across: Size bloat, Synergy, Monitoring.

Size bloat: When HVCs are developed in silos without adhering to any guidelines, they tend to use any and all solutions available out there. For example, an HVC may end up using Volley for networking, where as the app could be using Retrofit, an HVC may use Fresco and the app could be using Glide. You get the idea! Integrating HVCs may prove to be costly in these cases as it unnecessarily increase the APK size by adding redundant libraries which has the exactly same functionality.

Synergy: Many Microsoft mobile apps build policies and prioritization logic around storage, network calls etc. When HVCs use their own libraries, they end up bypassing these and the app loses control. Apps would want HVCs to leverage what they have implemented rather than using an opinionated and isolated implementation for core functionalities like networking, logging, telemetry, storage, image loading etc.

Monitoring: At the end of the day, HVCs are nothing but libraries that will be bundled with the app. Once released, the app has no insights into resource consumption, perf, crashes related to the HVC. For the integrating app, HVC is just another feature. But HVC would want to do everything best so that it shines. HVC may be doing lot of prefetching, caching, high number of network calls etc. that it thinks necessary to optimize itself. App on the other hand, wants to optimize for the entire user experience. Once integrated, the app is flying blind and has no insights into the resource consumption and side effects the HVC is having.

Issues for HVCs

There are 3 main issues that we came across: Layering, Integration, Guidelines.

Layering: HVCs do not have a standard set of APIs that they can assume to be available across any Microsoft mobile app. Because of this, they have to invent their own API abstractions or go with opinionated implementations.

Integration: Each HVC being developed in silo invents their own interfaces and ways to integrate with the app. For an app, every integration is a new investment with nuances specific to each HVC. There is no streamlined contracts available which can be leveraged so that integration with the app becomes smooth and quick.

Guidelines: HVCs do not have a common set of guidelines that they should follow. Whenever they attempt to market their component to the application, the conversation is lengthy and there are a lot of pushes and pulls. If there are a set of guidelines available upfront, agreed upon by the apps, the life of HVCs will become easy. More so, if some of this are codified as contracts.

Solution

Lets think about reuse within a single mobile code base first. A typical use case is to centralize networking aspects of an app. Lets say Network Impl module implements library integrations for performing network calls and all the logic about retry, prioritization, handling low bandwidth scenarios etc. Lets say this implementation is exposed via a set of interfaces collated into Network API module. Other parts of the app use the API module to make network calls.

Typical network module setup

Feature teams do not need to worry about the details of networking logic, retry, priority etc. All of that is the concern of the Network Impl module. Feature teams simply use the Network API module for all their networking needs. The app then would typically use an injection framework such as dagger to inject the concrete implementations for the APIs used from Network Impl module.

Network module used from various features through API module

Part1 — ApiService

Our idea is similar to what we do within a single app. We established an API layer that works across Microsoft mobile apps. The implementation will be provided by every app (there is a better way and we will discuss that later on, but for now, lets tolerate the duplication) and it needs to be done only for the first HVC they integrate. HVCs can take dependency on this API layer and simply assume that it would work. We extended the same concept for cross cutting and core functionality like Storage, Logging, Telemetry, Image loading etc.

HVCs leveraging common API layer, apps implementing them

For ease of adoption, the common API layer is developed, tested and released separately. This is the first part of the solution, which addresses two issues for the hub: Size Bloat and Synergy and two issues for the HVC; Layering and Integration.

Part 2 — IHvc API

With a common API layer established across Microsoft mobile apps and few HVCs starting to use them, we have a great opportunity to shape the interaction between the two. Since HVCs are developed as separate libraries, and they now need to be “injected” with the implementations for the APIs in the common layer, we codify the contract between any HVC and hub app. We define a ApiService object which encapsulates all dependencies from common API layer , we then define HvcContract type which can help in carrying HVC specific contracts and configurations from the app. Each HVC can implement their own settings and contracts by extending from HvcContract which will be the common base class.

/**
* Service class that consolidates all APIs from common API layer.
*/
data class ApiService(
val networkApi: INetworkService,
val loggerApi: ILoggingService,
val storageApi: IBlobStorageService,
val telemetryApi: ITelemetryService,
...
)
/**
* HVCs will extend this and add their contracts and settings.
*/
open class HvcContract {
val debugMode: Boolean = false
...
}

With this we give shape to the HVC interface which all HVCs should implement. Through this interface we pass the ApiService and HvcContract implementations.

interface IHvc<Contract : HvcContract> {
/**
* Initialize HVC with the given contract.
*/
fun init(service: ApiService, hvcContract: HvcContract<Contract>)

/**
* Lifecycle method to indicate the HVC should be destroyed.
* Dispose any cache / perform any HVC level cleanups.
*/
fun destroy()

/**
* Method that app can used to launch the HVC.
* Implementations can be to launch an Activity or obtain a Fragment, for e.g.
*/
fun launcher(): HvcLauncher
}

With this the hub app can initialize any HVC and launch it using the HvcLauncer . This approach provides consistent way of integrating any HVCs to any Microsoft mobile app. This is the second part of the solution and it solves one issue for HVC: Integration.

Part 3 — Orchestrator

With the common API layer and IHvc contracts, we can introduce a middle man: the Orchestrator . For Microsoft mobile apps, this Orchestrator will act as the central agent to instantiate any HVC and interact with this. From app perspective, ApiService remains same across HVCs, only the HvcContract changes per HVC. So it can initialize the Orchestrator with the ApiService . Orchestrator will provide an API to create an HVC instance for a given HvcContract . This way Orchestrator will hold the responsibility to pass ApiService to all the HVCs that it creates.

/**
* Orchestrator interface for apps to obtain HVC instances
*/
interface IOrchestrator {
/**
* Returns the HVC instance requested.
* When the hub app needs an instance of the HVC, they can use this
* method to obtain one.
*
* Assume that there is a registry with HvcContract type to
* identify the HVC to be created.
*/
fun <Contract : HvcContract, HvcClass : IHvc<Contract>> getHvc(
hvcContract: Contract
): HvcClass

/**
* Destroy the orchestrator.
*/
fun destroy()
}

With an Orchestrator implementation that is instantiated by the app that is the single entry point for creating any HVC, we have a tremendous opportunity. We can “proxy” the services in ApiService and internally log telemetry for things like resource consumption, latency, API usage etc. Proxy or implementation can also be used for throttling, applying app policies and other enforcements.

class Orchestrator(
private val apiService: ApiService,
policy: Policy = Policy.EmptyPolicy
) : IOrchestrator {

override fun <Contract : HvcContract, HvcClass : IHvc<Contract>> getHvc(
hvcContract: Contract
): HvcClass {
val proxyService = proxyServices(id, apiService)
return createHvcInstance(proxyService, hvcContract)
}

private fun proxyService(id: String, apiService: ApiService) {
// Create proxy for each service that we want to intercept.
// Create a new ApiService which consists of these proxied objects.
return ApiService(proxyNetworkApi, proxyLoggerApi,
proxyStorageApi, proxyTelemetryApi)
}
}

In each proxy class implementations, the Orchestrator can intercept the calls from each HVC, identify them separately (since proxies are created per HVC instance) and provide value addition. Some examples are:

  1. Logger proxy can append HVC Id in the log message, so that it is easy to debug.
  2. Telemetry proxy can add HVC Id into the telemetry events, so that dash-boarding becomes easier.
  3. Network proxy can measure the response sizes and estimate the bandwidth consumed. It can measure latency of each network call and log telemetry. It can also potentially throttle network calls based on Policy .

Apart form the telemetry from proxies, the Orchestrator can also log telemetry around HVC creation time, cache hit ratio, attach a crash tracking mechanism etc. There are endless opportunities.

This concludes the third part of the solution and this addresses Monitoring aspect for the app without either the HVC or the app needing to write a single line of code. Without the abstraction provided by common API layer and the IHvc and IOrchestrator contracts, it is not possible to derive insights into what an HVC may be doing once integrated into an app.

Putting everything together, the architecture will look like this:

Final architecture

Conclusion

Rearchitected HVC integration

As we can see the architecture solves for the major issues observed for both HVCs and Microsoft mobile apps. It solves the Size Bloat, Synergy and Monitoring issues presented by the Microsoft mobile apps and Layering, Integration and Guidelines (some of it which can be codified as contracts) aspects presented by HVCs.

Over and beyond, HVC code becomes more unit testable, services can be mocked easily and HVCs can create their own playground app to showcase and test their functionality. Apps can monitor the HVCs, set up thresholds for perf, resource consumption, crash rate etc. Apps who want to uptake the HVC can study the current usage and derive insights.

Future bets

This is just scratching the surface of the giant reusability problem in a large company such as Microsoft. There is reinvention happening all the time in silos. Same problems are being solved over and over again — “priority networking”, “crash tracking”, “feature usage scoring”, “data sync management”, “pre-fetching prioritization” and the list goes on and on. Establishing a common language across the apps gives us an opportunity to think about providing default implementations that are proven, well tested, optimized to be provided as libraries in Microsoft mobile ecosystem. With a thriving ecosystem of HVCs and such libraries along with the common layering, one can imagine creating a new app would involve assembling the libraries, common layer and picking and choosing HVCs and then focusing on the business problem that the app wants to solve. This can be super efficient and is the ultimate goal for us!

In the next article we will look at some real example and talk more about the journey of creating the first HVC and first app which adheres to this architecture. Till then, good bye!

Crew

--

--

Anand Nath
Microsoft Mobile Engineering

Works as Principal Engineer @ Microsoft Teams Mobile to solve hard problems by collaborating with some of the best minds in the industry.