7

The company that I work for has gone off the deep end with feature toggles-configuration keys that turn on/off particular behavior based on certain conditions. Martin Fowler actually refers to them as business toggles (http://martinfowler.com/bliki/FeatureToggle.html).

We have many clients all using the same service, but each wants slightly different behavior. Even worse, many want certain subgroups of their users to see different behavior. Thus, we use business toggles. The toggles have become a spaghetti ball of if/else logic that with occasionally unexpected interactions.

Are there any design patterns useful in managing a situation such as this?

Cheers!

Ian Ringrose
  • 49,271
  • 50
  • 203
  • 302
Kramer
  • 748
  • 2
  • 7
  • 26
  • Often a case of decision making and style and trade-offs rather than a design problem. People who go for a huge number of "configuration options" tend to either a) not understand the problem domain sufficiently or b) have a deep fear about "duplicate code". Either way, having a code base with N switches actually produces 2^N code bases and the illusion there is only 1. At some point it turns impossible to test the code base due to the sheer variety. Solution: Factor out all behaviors and find a design able to select a set of the behavior implementations. Then you can test again. – BitTickler Sep 01 '15 at 20:25
  • It's hard to give you a solution to your abstract problem, but look into the Specification pattern. That's a pattern that allows the make business rules explicit and allows to compose them to form different ones. Deciding to activate or not a feature could be done by fetching the specification associated with the feature and the current user/group of users/department (whatever granularity is needed) and ask the specification if it's satisfied or not given a specific context. – plalx Sep 01 '15 at 20:46

2 Answers2

4
  • The switches should be run-time switches, not compile-time ones. The reason is, that switches should be operable without re-deployment.
  • There are two possibilities to implement run-time conditional logic:
    1. if-then-else (or switch-case for multi-state switches)
    2. Polymorphy, which requires an object-oriented programming language.

I, personally, prefer to use plain if-then-else constructs, decorated with a comment saying TODO: Remove when feature has been tested. when applicable.

Keep in mind that every feature flag is a technical debt. Do not overuse them. I recommend against using them for business logic.


However, regarding your spaghetti ball of business toggles, I would recommend a refactoring.

  • No function should have a nesting depth larger than two. If it has, split into multiple functions with meaningful names.
  • Eliminate double negations.
  • De Morgan's laws are particularly useful for re-organizing conditional logic.

If that is not enough, you can model the business logic with a graph (e.g. decision flowchart) or a Matrix and use the respective standard algorithms.

ManuelAtWork
  • 1,513
  • 21
  • 27
2

I feel it is easier to answer the question using a small example:

Example problem: You have a component doing some computation from some input. Depending on customers, the following things tend to change:

  • Source/format of input data
  • Pre- processing of input data
  • Three different ways [A..C] to compute some output depending on customer use case.
  • Output format of computation results.

So the workflow might look a bit like this:

Preprocess -> Compute with either [A..C] flavor -> Format output -> Done.

A design I would consider to handle this without a bunch of configuration spaghetti would be:

  1. Classify for each processing step a Behavior kind. In the example for the preprocess step, this could be something like IInputShaper, responsible to transform a specific type of input data to the internal format and do some preprocessing. Possibly this could be split up again: IInputFitter, IPreprocess. For the other steps in the workflow, do accordingly. This leaves you with contracts for a number of behavior types (which is also good for understanding your problem domain. You can now easily see where the degrees of freedom actually come from, how many different kinds of behaviors the system has and you can keep the core implementation clean. Also you can test the core code base, you can document the requirements for each behavior contract and you can test the behavior implementations in a unit test fashion rather than "in vivo" - cluttered into the core code base.
  2. Implement each behavior against the contracts defined in step 1. Accept some duplicate code across the behavior implementations rather than over-designing and trying to factor out commonalities between those behaviors. Keep it simple. And have tests for each of them.

Result: A collection of behavior implementations with 0 configuration options and a core code base with 0 configuration options. Last step is to pick the behavior implementations for the customer project, maybe write a new implementation if there is something special.

If you did this right, you will live happily ever after without changing contracts and core system. If you missed the perfect design by a bit, you will have a few iterations until you figured out the best contract interface design so your core code base becomes stable.

With this approach, you will also have a much easier time to estimate work and write your quotations for new customers. You can simply check in quotation phase, if you have all behavior implementations already or if there are some new ones you need to write. And if you keep track of how long it took to write them in the first place you even will have a good guess for the man hours it will take.

BitTickler
  • 8,021
  • 3
  • 26
  • 45
  • I think that the OP was more struggling on how to pick the correct implementations and make that code manageable, but I could be wrong. I gave a starting point idea in the comments above for that matter, but perhaps it would make your answer more complete if it would address that concern as well. – plalx Sep 01 '15 at 20:59
  • @plalx Not sure. If he were looking for ways how to create such a design he would have given more information on language/technology he uses. This could be static libs linked together, DLLs, loaded as "plugins", messaged based agent instances, ... So I tried to stay on the level of abstraction he chose and only give the "pattern". – BitTickler Sep 01 '15 at 21:02
  • By how I mean... the configuration link itself, not how to actually load the implementations. For instance, address a rule such as all users that belongs to group A shall compute with the C flavor by default, unless they are also super users in case of which they would compute with flavor B. I think this is the kind of logic that caused the OP's sea of if statements, not how to actually fragment the logic of the feature into multiple strategies. – plalx Sep 01 '15 at 21:07
  • @plalx I had the problem with a product wrapping some third party code (X) with literally thousands of switches (#ifdef orgy) and some customer specific implementations for optional components. In the end it was not that hard to find out what to build for a new project: X version VX integration + optional components a...k as ordered. Sometimes X version VX changed the rules for some of the optional components. This caused a branch for those optional components. The people who did the projects had enough expertise to know the choices they had to make, rather quickly. – BitTickler Sep 01 '15 at 21:16
  • An idea to solve this would be to have something like `Feature 1----* FeatureConfiguration – plalx Sep 01 '15 at 21:18
  • @plalx Yes if you have a stable and defined build system you can reuse. I worked in embedded and the code was integrated into new embedded OS all the time - and sometimes we needed to accept a build system chosen by our customers. So that aspect was not really relevant for us. There was unavoidable porting and engineering per project anyway. – BitTickler Sep 01 '15 at 21:19
  • @plalx Now I get what you mean. You think about a runtime solution. In my case it was a link time approach. No matter which of both is used, in the end you need to system test the concrete result in any case. – BitTickler Sep 03 '15 at 21:49
  • @plalx your solution seems to be a good one. And seems to solve part of the problem. Namely, what configurations apply to which users. Implementing it is another issue (a political one, actually). It also doesn't solve the issue of 2^N potential sets of behavior for N configs, but nothing will solve that. – Kramer Sep 05 '15 at 17:48
  • @Kramer One difference is testability. If you have conditional code snippets in a monolithic piece of code you have different paths taken through the code depending on the conditions. And it is hard to impossible to be sure that those snippets will work together. If on the other hand you factored the "configuration points" in your code base and plug in behavior components, you have much better odds. They are isolated against each other (not able to accidently mutate core-code base state or somesuch). It is also easier to document when to use which behavior. – BitTickler Sep 06 '15 at 00:38
  • @Kramer I totally agree with what BitTickler said. For the 2^N issue it obviously depends how granular configurations have to be, but specifications could be associated to configuration members as well. – plalx Sep 07 '15 at 14:46