The content of this article comes from work I have been doing on three different projects: GetHuman, Swish, and a new secret app for my upcoming ng-conf 2018 talk called Super-Powered, Server-Rendered Progressive Native Apps.
In an ideal world, you could build one application that works consistently and perfectly on all platforms (i.e. web, iOS, Android, etc.) without worrying about the unique details of any one individual platform. Just code it up in some generic way and it just magically works everywhere. Unfortunately, we don’t live in that world today and any attempt to build a large multi-platform app under any other impression will inevitably lead to a world of pain, anguish and frustration (and/or a low quality app).
The problem at its core is that each platform has its own unique strengths that are often diminished or even completely eliminated by heavy abstractions. That doesn’t mean multi-platform development is bad. It’s just that (in most cases) it makes more sense to follow a slightly different approach. One that focuses more on building multi-platform products instead of multi-platform apps.
Before we go over this solution, however, let’s first talk in a little more detail about the problem.
As mentioned, the main problem with multi-platform development is when a higher level abstraction (i.e. a facade) is too heavy and ends up adversely affecting some of the strengths of the underlying platforms. The term “too heavy” in this context, however, is completely subjective and depends entirely on the requirements of whichever app is using a particular facade.
So, that begs the question: how do we determine whether or not a multi-platform facade is too heavy for an app?
Facades that abstract multiple platforms can be really useful and valuable AS LONG AS the following conditions are met:
In general, facades are more likely to be a poor fit for more apps when:
The idea of “write once, run everywhere” is an anti-pattern when you treat it as the goal instead of a means to an end. When your primary objective is to maximize code re-use, you are more likely to choose facades that are missing key features and/or have significant performance issues and/or are difficult to maintain.
Your true goal should really be building the best possible version of your product. That may or may not involve multiple platforms and that may or may not involve different levels of code sharing.
This article uses a lot of terminology that likely has different meanings to different people. The solution I am proposing for multi-platform development requires that we are all have the same definition of a platform, a product and an app as well as how they should all fit together.
A “platform” for the purposes of this discussion is a software environment that provides a set of constraints and low level capabilities for visual rendering.
One important thing to keep in mind is that there are different levels of platforms. Higher level platforms act as facades for lower level platforms. Here are some examples of different platform levels:
A “product” for the purposes of this discussion is one or more frontend apps created with the same language, managed together and designed to satisfy the specific needs of a particular market segment or mission.
The key point here is that a product is not just one app. In many cases, a product involves a number of different apps working together for a common goal. For example, one product could include both a NativeScript app and a separate Progressive Web App as well as an administrator console on the web and a static marketing website.
An “app” for the purposes of this discussion is the smallest possible deployable package of frontend views that solve a specific problem for a set of users on one platform.
There is quite a bit to unpack here, but I would imagine that this definition doesn’t necessarily match up with your own. Most people jam way too many features and give way too much responsibility to individual apps. In general, it seems much easier to simply add another feature to an existing app rather than build a new one. That is why there is a tendency to build a large, custom multi-platform facade that eventually becomes an albatross around the neck of an organization.
So…let’s not do that.
Instead of creating one massive app, let’s create one product that consists of many small apps where each deployable app unit does one thing really well for one platform.
There are many different ways to configure a product with small, single platform apps. Here is an example of one way to to do it for a JavaScript-based product called foo:
This folder structure here depicts the code in a product monorepo that has a “platform sandwich” architecture:
Again, this is just an example of how to set up your product code. It is not the only way.
At a high level, the goal with this approach is to get the best of both worlds: easy access to the full capabilities of any given platform while still maximizing code reuse. Specific benefits include:
I started this article with the premise that almost any non-trivial, multi-platform app cannot be built in a completely generic way (i.e. the app will inevitably have logic for the underlying platforms somewhere).
But…things can change.
10 years ago, web developers often had to write code that specifically targeted Internet Explorer. Perhaps in 10 years from now, the web will make another huge leap to become a true first class citizen on all devices.
Who knows if that will happen, but if it does, then building for just one platform (i.e. the web) may actually become viable one day.
As I mentioned earlier, we use the techniques described in this article for our spending tracker app at Swish, but I also have some other related upcoming events:
If you have thoughts on this topic, please post a comment here or reach out. I am doing a lot of work in this space and could use any and all feedback. Thanks!