HACKER Q&A
📣 mettamage

Diverging products: fork codebase, create config file or other option?


Hello hello!

I was wondering about your experiences, since this problem is quite common.

You work at a company that has a flagship product. Suddenly they need a slightly diverging product from it (that's how it starts out). For example, the B2B version gets a B2C version, and the B2C has some extra features that the B2B version doesn't need (e.g. credit card payments, slight UI changes). Later on, the team finds out that the B2B version also has specific features that the B2C version doesn't need to know about.

The team is at the beginning of all of this.

The team could:

- Split the codebase with a fork

- Add a configuration file and have checks inside your code

- Do something else

How would you go about this? And what have you experienced before?


  👤 akavel Accepted Answer ✓
IMO, try to avoid fork - you'll never be able to merge it back anymore, a fork splits the product into 2; think what will happen when you get +1 more "special" customer.

In the 2nd company where I encountered this challenge, I introduced a single global cross-cutting interface, giving de facto hook points in the code. Implementations were specialized per customer; the default behaviors went by "vanilla" (90% were empty implementations). This still led to a mess and complex indirections, but at least we were getting some isolation and encapsulation (a.k.a. decoupling). In particular, in case we lost one customer, we could quickly scrap all the specialized cruft with a single clean cut. And let me tell you, they wanted crazy customizations sometimes, and we were not in power to resist. edit: Also, when analyzing, you could at least keep in mind only the common core + single customer's tweaks; other customers were completely off your mind. And you could try and see how this customer's tweaks interact between them in one place. edit 2: Note that some behaviors were still kept in the main codebase if they looked reasonably useful for future, and just the choice would be in the customizer then. Sorry if what I wrote is too chaotic :/ edit 3: Still, remember the "[refactoring] rule of three" - as applied here, first just hack your new behavior into a personal fork as if it was the only codebase, and only when it works look where to introduce decoupling points. You will 99% always see them much better from position of hindsight than when trying to predict them initially.

That said, I'm not yet aware if there's a way to reasonably mitigate a huge increase in complexity when supporting multiple customers.


👤 mettamage
So based on the comments, I found out about the following approaches:

- Feature flags

- Configuration options / software product lines

(see a recently published article w.r.t. the difference [1])

- One HN comment was about plugins, but when I Google *"feature flags" vs "plugins" I'm not finding much. I have the feeling that hooks/plugin systems are part of the feature flags idea (not sure about this).

[1] https://www.cs.cmu.edu/~ckaestne/pdf/icseseip20.pdf


👤 elviejo
what you do, is stablish a kernel of libraries, that are common to both systems.

this is called a "software product line" it has been studied for decades.

here are some studies from Carnegie Mellon on the topic.

https://resources.sei.cmu.edu/library/asset-view.cfm?assetid....


👤 adrianmsmith
In addition to the other comments about not forking, I will add this disadvantage of forking:

I have thought in the past that if your forking technology is a branch in git, you can still merge changes (or cherry-pick, etc.), so all is not lost in terms of creating features or fixing bugs which should affect both platforms. However...

The problem there becomes refactoring. If you're e.g. using Java, with e.g. IntelliJ, you can e.g. rename methods and all usages will also get renamed. That's good, and that feature doesn't just speed you up, it also encourages you to do the refactoring at all, leading to better code long-term.

However, if you have n branches, IntelliJ is only going to refactor the branch you're on. After you've merged the change into the other branch, all the features custom-developed for that branch will still use the old e.g. method name, you'll have to update those usages by hand, which is a lot of work.

If you keep the code in one codebase (e.g. feature flags, config file, whatever), if you refactor something, all the usages (even in code hidden behind the feature flags) will also get renamed.


👤 simonw
Definitely feature flags.

Forking the codebase will lead to much regret further down the line as the two forks continue to diverge and you spend most of your time trying to update them simultaneously or building features twice.

Feature flags mean you can keep all of your functionality on your main branch with the least amount of additional work.

This is a great guide to them: https://martinfowler.com/articles/feature-toggles.html


👤 thedevindevops
On the config options side can I recommend a decent Dependency Injection framework will make your life a lot easier, as you can have everything configured at startup without having lots of 'if's in the code checking the flag state. Combined with good abstraction this can make the whole thing much easier to manage and the whole config for a given client can be checked in one place.

👤 brudgers

👤 crazypython
Depends on your codebase. JetBrains use a fork and rebase model.

👤 dyeje
Hard to say without more detail, but this seems like a good use case for feature flagging.