Single Repo System for Multiple Instances
We implemented a “Single Repo System” a while back successfully, where we added features for multiple domains embedding small websites inside a website. In this article I’d like to extend this model to multiple instances. For instance, there could be different features on different instance, even for the same feature, it might be subtle workflow difference for each instance. Most of commercial products these days, when sold to customers with different needs, are having this built in core.
The gain of using single repo is, obviously in the sense, that most of features done in the past could potentially get reused for another customer therefore saving development work. Although this is apparent, it’s not necessarily correct when number of customers isn’t large enough, especially when one customer need is very different from the other one. This becomes the major risk from the business development point of view. Putting the business risk aside, we need to address couple of key technical questions in terms of reducing the development and maintenance cost.
Major business risk is that we might end up with a) small number of instances b) heterogeneity of each instance, which ultimately will not justify the cost of staying in single repo.
Configuration
When we branch out to support multiple outcomes in programming, we write a if
statement. There's no exception supporting multiple instances in this case. Sooner mostly than later, the if
will show up in the code, but the strategy here is to, a) dodge b) hide c) defer d) avoid, basically try as much as we could to not write it, at least not directly. Since the number of if
and the way we write it can ultimately determine the quality and maintenance cost of the project.
No
if
is the ultimate goal. It has been almost proved as axiom. It doesn't mean you can't writeif
in general. The common sense we should have is that the less we use it, the more you are certain about the pathway. And the ultimate goal of any development is to be certain, especially when it comes to debug.
What is the alternative if no if
can be written?
Configurational settings, such as the following gives us some insights
{
default: { home: true },
instance1: { trading: true },
instance2: { report: true }
}
How does the above line change anything? Maybe let’s start with what it does tell us,
- anticipation of a feature
- initialization of the feature settings per instance
- centralization of features for all instances
- easy to understand, modify and deploy
If you take some time to think of the above items, the thing which configuration does differently is that, instead of speculating on the possible branching patch in the end, we’d like to deal with it and making it part of the architect. We will anticipate the change, initialize the persistent storage for it, and enable us version control this information. This way the cost of adding a feature to the configuration becomes relatively more straightforward to estimate, simply because we are writing it done. Business might already have an excel sheet listing this feature as well as the cost, the idea here is comparable to that.
Execution
Suppose configuration is the way to go, will it solve all our problem of having multiple instances in the single repo? The answer of the question lies mostly in the change we have to make to accommodate it. Similar to “Single Repo System”, the execution unfolds with folder structures, naming conventions as well as dependency arrangements. In this article, we’ll focus on the structure.
Separation of concern will continue to be one of the leading metric for system architect. It gets more emphasized with multiple instances, especially when single repo is maintained.
The separation of concern is going to be big deal, because one change in one instance, in theory, shouldn’t have any impact to another instance. The ideal case is that multiple instances go through separate build stages therefore the deployed version is entirely built, optimized, and served as a unique version, irregardless if their source code are compiled from the same place.
- src
- instance-1
- instance-2
- utility tools
index.js
index-1.js // imaginary branching
index-2.js // imaginary branching II
In case we’d like to share utilities, we can branch out the folder structure below src
folder. Different instance has its own root file, ex. index-1
has its own pipeline of bootstrapping the app, before reaching there, the system had already read the configurational settings, thus 1
is tagged after index
. This file could be dynamically generated, and it's presented here for easy understanding.
- src
- instance-1
- pages
Home.js
If configurational settings drives us to have a page, it’ll look into its own instance folder, find the right route there. Things from now on is almost same as single instance development. The key here is that the decision making isn’t given to page level.
We won’t give chance for individual page to judge which feature it’s going to implement. The decision has been made early, when it comes to the route and page, it’ll be pure execution, without hesitation.
There shouldn’t be any concern a feature line might accidentally be implemented from another instance, since page don’t share same code location. There’s tendency for reusability from one instance to another to speed up work, however this reusability lies mostly on the willingness of the developer borrowing from the core library, the third party library, or the upper level utility folder. In another word, reusability here is a side product and a very nice to have one. But it can’t be the reason to create additional separation issue.
From operational point of view, If for any reason, we don’t want to support an instance anymore, we can simply delete its folder. Similarly if we want to add another one, copy paste should give you a good head start with almost no cost. Basically the solution is highly scalable as the number of instances increases.
Conclusion
The idea of using configurational settings is presented for multiple instances with a single repo setup. The goal is to measure the potential unknown pathway from the higher level, therefore minimizing the short and long term development and maintenance cost for the overall project.
Reader might wonder how to write code without
if
. It's not uncommon. The single statementoptions["Home"]
can be used to replaceif
. Of course it depends on if you can have all theoptions
list and stay within one of the choice 100% of the time.