This blog post is part of a series of blog posts, which aims to shed some light and share from our experience working with React Native at Wix.
When you build a great product it will eventually attract more and more users who will ask for a new set of features and expect the best performance. In the case of a small startup, simple React Native application architecture is great! With just a handful of developers, assumingly all working on the same repository, you are able to manage your development and release process in such a way, that developing one feature doesn’t interfere with working continuously on the others.
As the company grows and more developers join the team, and you still have to deliver features at the same velocity as before — you might feel that it’s becoming more difficult to move quickly if you stick to your simple app structure. One feature breaks the others, the build process is unstable and takes much longer than before, and it’s kind of impossible to work on a single repo. If you’re not prepared for these, your code quality will drop and that will eventually be reflected in the quality of your app. You and your teammates will be frustrated by this velocity slowdown, and you will potentially lose your audience.
The example above is probably true for every software stack, but it’s even more complicated with React Native; Because we are working in 3 different environments: iOS, Android and JS, we have to deal with NPM, Gradle and CocoaPods. Do all team members need to be familiar with all of these in order to release a small feature written in JS? Well, no.
At Wix Mobile group, we scaled up our team pretty fast. We tripled our headcount in a few months and understood that in order to grow without compromising our development velocity and application quality, we needed to think differently.
For instance, let’s take a look at a simple React Native application structure:
At the top level, we have our application business logic (red), including UI components written by JS and React, which is actually where most of our codebase would be. At the mid-level, we have the React Native framework (purple) as a bridge to the Native SDKs. If you want to use any additional functionality which is missing in React Native (e.g. use a camera API, get contacts or use GPS) you should implement this missing functionality or add an external library (orange) to your project as a dependency. Furthermore, we have JS libraries (yellow) as utils (e.g. Moment, Lodash) or common code with your web projects.
With this structure, you may face dozens of different issues when it comes to scaling — a few general sources of your problems may be related to:
- Ownership: Code ownership is when one team\person in an organization owns a codebase. The individual is responsible for making changes to his or her code, making decisions about design, and managing bugs. In a single project, the ownership should be well defined, but with a simple application structure (single repo), the boundaries are not clear.
- Velocity: Both development and release velocity are super important for the developers. Since React Native projects include Native and JS codebases in the same project, it forces us to compile and build the whole native project again and again, even for a small change in JS. We should strive for full independent development within every team, building only the code the team is responsible for, and get all other dependencies as a pre-build; no need to rebuild untouched native code.
- Testing: It becomes hard to test your features and code with other 50 developers pushing code to your repository. Each team has its own testing techniques and practices, so there is no reason to have all your application tests in the same place. It makes much more sense to be able to test the smallest piece of code that was changed in an isolated way, without being dependent on other code.
Once we realized all of that and understood how quickly we are growing, we came to the conclusion that it won’t be possible to achieve our dreams with a simple React Native app structure.
When you design an architecture, it should fit your organization structure.
Wix Organizational Structure
Wix is divided into multiple entities, each called a Company. Each Company is a product-oriented entity working on a set of features for a specific product. For example:
- WixStores deals with all the features that an online store needs, such as a catalog, prices, delivery etc..
- WixMedia works on all features related to the media needs of Wix users, such as saving images to the site, editing video and broadcasting video or music etc..
You can think of each company as a fully functional and independent start-up. In addition, we have a professional entity called the Guild, that groups employees according to their expertise. For example, we have a Server Guild, a Mobile Guild, a QA Guild, and more. Each guild is responsible for hiring, educating and aligning its developers as needed. This structure is designed to ensure we can move quickly as independent groups, instead of competing for resources with other teams.
In addition to that, some of the Companies have Full-Stack developers, who develop their features end-to-end, from the database to the user interface, so they can contribute to the mobile app without any native (iOS\Android) experience.
Taking all of the above into account, we defined our goals for the new architecture as follows:
- Independent development: With isolated teams, each team’s codebase can be developed and tested as a black box but with the same infrastructure.
- Independent deployment: This enables each developer to ship new features to production ASAP, just like a web developer. It is another step toward real continuous-deployment that currently is not possible on mobile.
- Be Native agnostic: At Wix, we have way more web developers than mobile developers. We aim to reach a point of not needing native knowledge at all (no need to open Xcode to check your features on an iOS device).
- Stable infra code as a dependency: To minimize disruption in our day-to-day work, developers should be able to develop while using production infra code, without depending on special code developed only for DEV time (example project).
The monolithic approach doesn’t work for large projects
Having a monolithic approach for large mobile applications becomes unwieldy. There needs to be a way of breaking it up into smaller Modules that can act independently.
The idea behind Multi-Module Architecture is to think about an application as a composition of features that are owned by an independent team. Each team has a distinct area of business or mission it cares about and specializes in.
You can also think about it as an operating system (OS) and apps — each team has its own mission and all team features (mini-app) are bundled into a single application, based on the same OS.
How Does it Work?
The key to success for this mission is Separation of Concerns. Backend developers call it micro-services, frontend developers extend it, and invent the micro-frontend architecture. This separation can be in different ways, vertically (by feature), or horizontally (by layers) — we decided to mix and match between these 2 variations.
If you think about it, your application codebase includes 2 parts: on one side, the infrastructure which is used by all teams, changes more rarely, has a different release cycle and requires an extensive QA, and is more sensitive to changes. On the other side, the product business logic itself, which is the UI Components, user-facing flows which has a much faster release cycle, developed by multiple teams, most of the time not related to each other and it can be referred to as an “extension” to the application.
With this in mind, we separated our codebase into two main entities: Engine and Modules.
The Engine wraps all the infrastructure of our React Native application into a single package.
The Engine includes iOS and Android native projects, React Native framework, all 3rd parties libraries with native code, and any JS infrastructure code.
- The Engine is primarily the OS of our application.
- It is the entry point of the application - When the app opens, the first line of code is the index.js of this project.
- The Engine holds all the infrastructure for all developers developing for the application (Push Notifications, Error Handling, Network Layer, Localizations, Accessibility, and many more), so they can focus on writing products.
- The Engine is developed by a single infrastructure team, and includes senior native developers, as well as React Native experts which are familiar with how React Native works under the hood.
- Since the Engine is responsible for providing the whole native code, it can provide it as compiled executable packages (APK, APP).
The last point above is radically the most important part of our architecture, this Engine holds the binaries of the app, so other projects can add it as a dependency, install the pre-built binaries on the device, but with their own JS code.
If you are asking yourself “how is it possible?!”, keep reading.
As I mentioned before, at Wix we are divided into small product groups (Companies) that work independently, which led us to split the JS layer into smaller parts, developed and maintained by a separated team.
- Module is a mini-app, inside Wix application, written as an independent encapsulated production business logic.
- Module is written only in JS (or TS), no native code. The team doesn’t need to hire native developers.
- Modules can of course communicate with each other, even though they implemented different products, we have tons of integrations. For example, a Module can act as a “settings service” for the whole application, so it can be used by all Modules to retrieve information about the logged-in user and so on. The fact that the development process of a Module should be independent, should not limit the product in any way.
- Each Module should implement an interface which serves as its contract with the Engine and other Modules.
- Modules can have JS libraries as dependencies, they can be utils libraries, shared code between Modules, or between mobile and web.
- Modules are using the Engine infrastructure as a service.
- Modules are published to NPM as package (to a private registry).
The New Structure
Now, after we are familiar with Modules and Engine, let’s talk about how they all operate together?
In the diagram above, you can see there are 3 types of entities: Engine, Module, and Wix App.
Wix App is the main repository, that includes the dependencies to all our Modules and to the Engine. In fact, the Wix App repository is just a configuration file and list of dependencies and doesn’t contain a single line of code. The entry point of the application is the Engine index.js, which reads the configuration file from the root folder of the project, with the list of all the Modules.
The biggest change we did is to take pre-build native images (APK & APP) outside the main repository into a separate project, in order to allow developers to run part of the application with the same native project. This concept is similar to Expo, tailored specifically for Wix, so instead of downloading the Expo app from the Apple/Google stores, Wix modules get APK/APP files as a dependency from the Engine. With this approach, all teams get the same native environment for development, Modules’ developers do not need to touch any native code, which saves time for builds and solving native integration issues.
In case a module’s developer needs to use a native dependency, he should ask the Engine team to add it, or PR to the Engine project. In fact, this approach breaks the clear ownership idea, but it helps us to reduce build time, carefully examine libraries with native code by native developers, and eliminate the fact that 2 modules bring different native versions.
Modules can communicate and transfer data between them, but all the communication is through the Engine.
There is no direct communication between Modules and no direct requirement between them, in order to control inner communication, add contacts validation more easily, and to support lazy loading of Modules, so Module code won’t load to the memory until another Module asks for it. Communication channels are varied, Modules can expose functions and components to be used by others, and even register to each other broadcast events.
Since each Module and the Engine are being developed in separate repositories and published as a different NPM package, each of them can have a different release cycle.
In our case, a new Engine version is released every 2 weeks, since it includes sensitive infrastructure code and requires an extensive QA cycle. But Modules’ owners release a new version almost every day, independent from each other. This approach is fundamentally the same as micro-services, releasing a small part of your code independently.
Once a new Wix App version is released to the store, the app is being built and bundled with the latest stable version of all Modules and the Engine. We currently hold more than 40 Modules in the app, developed by different teams.
In terms of goals, as we defined earlier in this blog post, we basically achieved them all (and a few more):
- Independent development: Each team (Company) can create a new Module for its own set of features, the code can be tested without being able to break by others.
- Independent deployment: Release a new version of your Module whenever you want, without caring if other teams are ready.
- Web Developers > Mobile Developers: This is the beauty of not needing to know native at all — no need to build the native project on your computer, it’s already ready for you.
- Identical dev-production environment: All teams get the same native environment for development, the same as production.
- Easier React Native upgrade: With a single team maintaining the native project and the infrastructure of the app, upgrade to a newer React Native version is much easier. As you know, React Native can include a lot of breaking changes for a new version, but now most of the changes are being made in the Engine, without interrupting Modules’ owners in their daily routine. In a lot of cases breaking changes are handled and encapsulated by Engine’s APIs so the upgrade is transparent for developers.
- Different Release Process: Since the Engine is the only part that includes native code, we can update the code in production in 2 ways: OTA (over the air) every time a Module releases a new version, or submit a new app\APK to the AppStore and GooglePlay for new Engine releases (with native code change).
Implementing a new architecture is a big challenge on its own, we had a lot of challenges, but at the end, it was a great success adopting Multi-Module architecture for our React Native application.
Leveraging our organizational structure, and using Multi-Module architecture has changed the dev culture at Wix Mobile. There are no dedicated iOS or Android teams, and we have web engineers regularly shipping code to our mobile app with barely any need for native engineer hand-holding. We allow the teams to have freedom on the one hand, and unified product, UI, and infrastructure on the other hand, even though it’s developed by different developers around the world.
Thank you for reading!
In the next part of this series, we will deep dive into practical examples of using Multi-Module architecture, by reviewing step by step the Engine and Module implementation.
You can take a sneak peek at this repo in the meantime: https://github.com/wix-incubator/react-native-wix-engine
If you’d like to get updates, follow me on Twitter.
In this series of blog posts, I outline our experience with React Native, share our best practices, and what’s next for us.
Part 2 — The Architecture I
Part 3 — The Architecture II (Deep Dive) [coming soon]
Part 4 — Performance, Performance, Performance [coming soon]
Part 5 — Tools [coming soon]
Part 6 — Open Source [coming soon]
Part 7 — Testing [coming soon]
Part 8 — Challenges and future plans [coming soon]