From Monolith to Micro-frontends: How we Revolutionised REA Mobile App Development
On 21/3, REA Group ran its second Unstack’d event, as part of our initiative to share our tech to the outside development community. This time, the theme was on a mobile related topic.
Here, I’m transcribing the shared item into a blog, for easier readable form. You can also get the recording from here.
The mobile timeline at REA.
REA started in 1995 serving our content through web mainly.
With the rise of iOS by Apple, we introduced the iOS Residential App in 2010, and iOS RealCommercial App in 2012.
Google then came along and introduced Android, another smartphone platform that gets popular too. In 2014, we then introduce Android Residential App, and 2016, Android RealCommercial App.
Follow on, mobile platform continues to grow. We then introduce 2 more apps in the market, i.e. the Ignite App, and Flatmate App, for both iOS and Android platforms.
The Residential App Timeline
In this talk, the focus is on our main Residential App.
We setup an iOS team back in 2010 for our iOS development. Then we setup a different team for our Android Development in 2014.
Fast forward to today, we have about 10 different teams which consist of both iOS and Android the contributed to the Residential App development.
The focus of this talk is on, how we evolve our architecture to enable scaling from 2014 till today.
Goals and Guides.
In our initiative to head toward a better architecture to we have to some goals we would like to achieve, as well as some guide which form the base of our development.
The 3 goals we want to achieve at high levels are
- Development Scalability – We want to ensure as we need more features, we can easily add them scalably
- Development Flexibility – We want teams to have the flexibility and autonomy for tech and design evolvement without stepping on each other toes.
- Developer Experience – We want our developers to have great development experience, and love to contribute to the code base.
At the same time, we have some basic guides for our mobile development
- We want to get as close as possible to the platform in our mobile development, as it is more reliable in a long run. Hence our development is using the Android and iOS native development platform.
- We want to use the latest industry trend technology stack for our mobile development, to ensure we are not dated. We want all new features to use Swift and SwiftUI for iOS development; use Kotlin and Jetpack Compose for our Android Development.
The Three Eras
To provide and illustration of how we get into where we are today, it’s good to look at how we have journey through the different eras of our mobile development.
We break our development phases into 3 eras. In this section I’ll talk about the 2 eras, before heading to the main topic of this talk, the Micro-frontends Era.
The Monolith Era
As we know, we started the iOS Team ahead back in 2010. We learn much about iOS development. We follow the rapid change of the platform, we learn, grow and evolve all within the same code base, It has fragments of various iOS development tech stack in the code base.
Then in 2014, we started a different team, the Android Team. Similarly, we learn, grow and evolve all within the same code base.
At some point, we also hires some external contractors to contribute to the projects too. They are here to also up-skill the team and evolve our code base further. The contractors completed their tenure with us, and leaving behind our own Android and iOS team continue on the projects.
Throughout this period, it’s great we manage to ship apps that reached millions. Nonetheless, the structure we have does have some clear issue.
- iOS and Android teams are vastly different, with different architecture and code structure
- Due to rapid tech changes and evolution, with people movement, our code structure turns messy.
- Mobile industry continue to grow. A single team of mobile development is a bottleneck to delivery
- Mobile is still a byproduct served by common Services for the web. Hence often mobile development often has to be accommodative.
With those challenges, we evolve our development into what we call Federated development.
The Federation Era.
The Federation Era are here to solve the problem we have in the Monolith Era
Firstly, we combine the iOS and Android team to form an Architecture team. This is great as we can make the structure across two platforms more consistent.
This architecture team is also responsible for a new Mobile prioritised service, which is commonly known as Mobile Backend for Frontend (BFF). This Mobile BFF shield mobile development from Services changes, and give mobile the first class citizen services it needs.
We also form a Feature Team that is responsible for all the user facing features development. This is also a joint iOS-Android team structure.
To scale our development, we allow teams from other businesses to contribute to our code base. They build federated modules. Over time, other than just the module, they also contributed to the feature.
How Federation Era Helps.
With this in place, we solve all problems we have in the Monolith Era.
- We have now combined iOS and Android team together to ensure code to be more consistent
- We have better modular code structure partitioning the architecture layer and feature layer
- We scale our development allow other teams to federate into our code base
- We now have a specialized mobile BFF that shield our Mobile development from services changes
Nonetheless, from the structure above, we can we some issues.
Challenge With Federation Era.
- As we still have a single code base for both architecture and federation, its custodianship becomes unclear across both the architecture and feature team.
- While we have some structure, the boundary is still relatively vague. What is called a feature, what’s called a sub-feature?
- To contribute to the code base, the federated team need to conform to restrictive pattern. They come and go, and never have sense of ownership
- The Mobile BFF are not designed for federation. It is now congested with contribution from many teams.
In seeing these challenges, we further explore how we can improve.
This is how we evolve to the Micro-frontends Era.
The Micro-Frontends Era.
The idea of micro-frontends came from micro-services.
While it looks like modular development, it is different. The keyword is independently deverable frontend applications*.*
It’s like instead of building a big house, can we build small houses together and combine them together for a collaborated functionality.
App as a Platform.
Another way to look at it, is like making our App as a Platform.
Take for example, we have an Android Device. Anyone can build an App and put into the devices if they use the Android SDK Provided
Similarly, we treat our App as a Platform. It is like a top layer, where we call it App Shell Layer.
If any business like to add a feature into the App, they need to build their feature using our foundation libraries provided. We name that the Foundation Layer
The business teams can build little apps, that can be bundled together into the App Shell, and get shipped as one App for our end users. These little apps, we called them Micro-frontends, or in short (MFE). This layer is also called MFE Layer. This little App can be individually distributed into our App Library, and tested individually.
The App Shell Layer
To further illustrate how this architecture works. Let’s dive into the first layer, App Shell Layer
It has several responsibilities
- It is there to configure all the initial need of the App
- It is the entry to the App, where it startup it application, restoring the state and handle any deeplinking
- Last and most important piece, the App has the AppFrame and Routing. This layer is responsible to host the MFEs and show it to the user
To illustrate this better what AppFrame and Routing is, refers to the right side of the diagram above. The AppFrame are like compartments where one can place the MFEs.
Then we have a list of MFEs, where they can be placed in the respective compartment in the AppFrame through the routing mechanism we have. We also link the deeplink to our routing mechanism, so that they all work as a single consolidated entity.
From here we might be thinking each MFE is a single screen layer. But it is actually not. It contain more than just a screen layer.
To understand this better, let’s zoom further into what each MFE can possibly look like as shown in the diagram provided below.
Here there are several MFEs, each of them is more than a screen itself. It’s a User Experience.
- They can content a multiple screens that is next to each other like we have on an Ipad
- Or when they are in iPhone mode, they can be a single screen, that can click deeper into the detail, can come out.
- Or they can be like a wizard style, that links from one screen to the other
- Sometimes, they can expose more than one screen to be routable from the AppFrame
From here, you can see, each of them is a User Experience itself, rather than a single screen.
The MFE Layer.
Now, let’s get into the MFE layer.
Each of these MFE are fully owned by the business unit responsible for the feature. They can all be compiled as individual app, and shipped to the REA App Library for testing purposes
Upon ready, they are all bundled together and compiled into our main App. The main app can then be shipped to both REA App Library for testing, as well as PlayStore and AppStore for external usage.
Aligned Autonomy.
From here, you can see clear AUTONOMY by each team as
- We have clear separation and ownership
- Each team have the flexibility of Tech Structure, and it’s design
- They are smaller code base, easier to work with, and
- Each is shippable as an App by itself
At the same time, they are should be well ALIGNED, where by
- They have to use REA Foundation
- hey are linked to other MFE using routing and containable in AppFrame
- No MFE Should be dependent on each other
It we look at both, this is what commonly known as ALIGNED AUTONOMY.
No MFE Dependencies.
One of the requirement for MFE development is, there should have no MFEs dependencies. For 2 MFE to communicate or have related functionality, they have to either
- Use Routing
- Use REA Foundation
- Or even at time duplicate the functionality across the MFEs
We want to avoid as much as possible things that have to be common across few MFEs. However, at times we cannot avoid needing something common just between 2 MFEs, how do we solve that?
MFE Component Sharing.
We do that by MFE Component Sharing
If we’re to look at our Micro-frontend architecture, where we have App Shell Layer at the top and Foundation Layer at the bottom. In between, we have our MFE layers, with each MFE owned by respective team.
In most cases, MFE don’t have anything in common to share with other MFEs, hence such component part is not needed. (as per diagram above, MFE 3)
However, for the MFE to share something in common, we introduce the component layer. This component can share by the respective MFE owner and be accessed by other MFEs.
There are also cases some team doesn’t need any full screen feature, but would like to leverage the other MFE’s screen to show their part, They can have just the component parts worked by the team and screen it in other MFE Screen. (this is illustrated as Comp 3 above)
These two layers (MFE Screen and MFE Component) then form our MFE layer.
The Foundation Layer.
Lastly, the Foundation Layer, which is the essential development pack for our MFEs.
This is the layer that provides the common needs required by the MFEs.
- The foundation should not be just cater for any specific MFE need.
- They are useful to ensure conformity and consistency between the MFEs, by having the sensible defaults provided.
- They are also customisable and can be extended to be used by multiple Apps.
At REA, we have two distinct foundations that form this Foundation Layer
- Capability – this provide common functionality that is non-UI specific to the MFEs, such as Networking, Analytics, Authentication, etc.
- Design Kits – this provide the UI related functionality to ensure consistency across the design across MFEs, e.g. Color, Typography, Accessibility etc.
Extensibility of Foundation and MFE.
The long term plan for the foundation is created are not meant for just one App usage within REA. We want it extensible beyond an App.
Similar for MFEs, they can also be extensible beyond one app. But this can only be possible if we design the foundation suitable for it, as describe below.
Non-extensible design
Imagine we have X app that uses MFE 1, that needs Firebase Experiment
From an object-oriented design point of view, we implement the Firebase Experiment from the base foundation, and having MFE 1 linked to it. For Y app, if one needs Optimizely Experiments, then we have MFE 1 link to the Optimizely Experiment.
While this seems legitimate from basic OOP design, but the MFE is not Extensible beyond an App. The reason is because now MFE 1 linkage to the foundation has to tied to either Firebase or Optimizely. The MFE required modification to before it can be used by another app.
Extensible design
To avoid this issue, we need to design our foundation in a reverse manner
Here, we have our MFE linked directly to the base foundation only and not the specific.
For the specific, we have our X App and Y App configure its own specific foundation need.
This will ensure MFE is not tied to any specific foundation layer, and enable both the MFE and Foundation layer to extensible beyond one App usage.
How are we Doing?
If we’re to look at our MFE Architecture overall, and reflect with the original 3 goals we have. We have met all these goals!
- We can see that with MFEs, now it is scalable. We just need to add new MFEs as new User Experience to the App easily
- They are all independent from each other and have flexibility in the way it is designed and worked on
- Developer are also pretty happy working on the MFE, as they don’t need to worry about the entire code base, and just focus on the User Experience they are most interested in. They can explore free to explore new tech introduced.
Two Steps Forward One Step Back
As the saying, two steps forward one step back. We do encounter some pain along the way.
If we look at the table below
Micro-frontends is better
- It clearly wins in term of Clear Code Ownership
- Team Agility and Autonomy is also better in Micro-frontends than monolith
- Each feature can be built much faster in Micro-frontends
Nonetheless, we do regress, where Monolith is better for
- Easier integration
- Dependencies Synchronisation and
- Common infrastructure upgrade
In knowing these pain, we are now in the process of looking into, if there is a way we can tick all the boxes.
Not out of the wood yet… but we see the light shining
Clearly, we are not out of the wood yet, with some challenges ahead.
There are also some remnant of monolith features we have yet to migrate over the Micro-frontends architecture, that we need to work on.
But we do see a light shining.
We have a solution that does help scale our mobile development so much better than what we used to have, where developers are looking forward to work on.
Credits
Before ending it, let me share the credits to the following people
Thanks to the Unstack’d Team for much of the setup, review, and much plan to make this event possible
And thanks to to the pioneering architects and tech leads for spearheading the initial Micro-frontends architecture within REA.
And also a big thank you for the entire REA Mobile Teams who has relentlessly make this architecture possible for REA.