How to approach application replication
The problem of replicating an existing application
Let’s imagine that we have a thriving platform that we want to duplicate and adapt to another user base. Our second application might use a different paying system from the first one, or perhaps handle the user registration differently. Whatever the case, ultimately we wish to keep the majority of the existing structure intact. In such a scenario, copying the entire project and making necessary adjustments might seem like a good idea at the start - after all, it’s guaranteed to give us an entirely new platform in a fraction of the time needed for implementing the first one from scratch, right?
Technically, yes, but what happens when we want to implement a new feature on both platforms? Suddenly we have to tackle two separate implementations of the same feature. Not only does this require more time, but it also increases the chance of an error. By saving time in the beginning, we’re left with a technical debt that slows down the development of all commonly shared features in the future. Luckily, multiple strategies can make such parallel development easier and more streamlined. When we faced this problem with one of our clients, the first solution that came to mind was using a multi-tenant structure.
What is multi-tenancy and how can it be implemented?
Multi-tenancy is a software architecture solution that allows us to have multiple logically-separated instances of an application that are physically integrated as a single instance. Each 'tenant' (a group of users) gets their respective instance with a dedicated set of data, configurations, user management, and other properties. It might sound complicated, but most cloud-based services today are multi-tenant, therefore chances are that you’re familiar with at least one such application - take Wordpress for example!
This architecture can be implemented in several ways, depending on the database layer organization. One way would be to use a completely separate database for each tenant. While this approach provides the highest level of data isolation, it also requires a separate server instance for each database, resulting in a higher cost of using the application, complicating the server infrastructure, and ultimately making it more difficult to support. An alternative is to use the same database for all tenants, but give each tenant their separate schema with individual tables. Separate schemas provide only partial data isolation, but they are a cheaper alternative to fully isolated databases. There is also a third option, which takes data consolidation even further - making all tenants share a single schema. In that case, the application uses a tenant ID to find and retrieve data from each row. The main problem with this approach is that each new tenant makes it increasingly more difficult to query, index, and update data.
What if multi-tenancy isn’t the right solution?
Multi-tenancy is a popular choice for a lot of modern applications for a reason. Since all data is kept in the same place, it makes data aggregation and mining that much easier. Likewise, running just one physical instance makes the deployment process much more manageable. That being said, we quickly realized that it wasn’t the right strategy for the problem at hand. Initially, we thought that it would be a good idea to serve two slightly different versions of the same application on the same server, but in agreement with our client, we ultimately decided against that approach and abandoned the idea of a multi-tenant application altogether.
Having decided that we want to keep our two applications separately hosted, we were still left with the issue of having to implement future common features and improvements (at least) twice. After some consideration, we chose to extract all common React and Rails code in their respective packages, so that they can be plugged in and used in any number of our client’s platforms. This saves us from dealing with duplicate code and repeated implementations and bug fixes.
What impacted our choice
While we (and our client) were deciding on which solution to pick, we had to take several factors into account. One, how would multi-tenancy affect our deployment process? Keeping all applications on a single server means that, if we wanted to deploy one application, we would have no choice but to turn on the maintenance mode for all of them. Though updating all applications with a single deployment is highly beneficial in many cases, we wanted to keep the deployment processes individual for each application so that we can release application-specific changes.
Two, how would multi-tenancy affect the quality of service? As briefly mentioned before, the more tenants we would introduce, the bigger the effect it would have on the data fetching speed and on memory consumption. We would also be facing the threat of client memory bloat, primarily due to the way Rails connects to the database.
Three, what would happen if one of the platforms goes down? Unfortunately, one of the key elements of the multi-tenant structure is the fact that everything is hosted on the same server. If one platform goes down, all other platforms are going down with it.
Having a separate codebase and server for each platform helps us avoid all of the pitfalls mentioned above. While this inevitably increases hosting expenses, it also allows for more flexible Heroku configurations and complete independence from other platforms. If one platform requires more resources than others, we can upgrade add-ons for that specific server. Another advantage of having them on separate servers is that if one platform goes down, others are not affected at all. Lastly, in case that the two applications start to differ more and more over time, they are much easier to separate in this type of structure.