Design pattern for conditional behaviour depending on a client ID

anti-patternsbehaviordesign-patterns

I don't know if there actually is one design pattern, or several together, or maybe what i'm asking is anti-pattern.

Context (simplified) : my company sells SaaS services (through an API) to several clients, which in turn sells this service to end users. Each client uses the service, but some want custom behaviours in several features. Obviously, the custom behaviour should only occur for that specific client. The end user is linked to one specific client, so when using the service, we know which client identifier it is, so which behaviour to have on the requested feature.

My question is : by keeping only one code base, how do I manage different behaviours depending on the client identifier ?

The current implementation is obviously the naive and dirty one (simplified) :

function someFeature() {
  // common code

  if(clientId === 'someClient') {
  // does something
  } else if(clientId === 'otherClient') {
   // does something else
  } // else if ... for every other custom behaviour, or on other parts of the feature

  // common code
}

I've read about design patterns but they all seem to be focused on parts of my problem, and I don't see how to use them all together …

If I had to dig a solution I would probably go with an implementation of Bridge and a Factory to instanciate various classes (for each client) which would implement a same feature differently (each clients' custom behaviour). With that in mind, when a user wants to use a feature, the approriate class, thanks to the factory, would be instatiated and used with the desired custom behaviour.

This implementation looks like the correct way to do it but I think it would encourage duplication of code (for the "common code" bits), and would require the programmers to create maybe thousands of new interfaces/classes for each features multiplied by each client's custom behaviour, sometimes just for a simple line of custom code.

What do you think would be the proper way to solve my problem ?

Best Answer

Thinking about what design patterns to use usually isn't that fruitful an exercise. Think about what properties your solutions should have.

Obviously having if statements checking the client ID sprinkled throughout your code isn't going to scale well to lots of clients. You probably want lots of clients, so you want adding a new one to be simple. So you're going to want all the client configuration in one place, as much as possible.

Also, maybe in the future somebody will want a feature to work like client A for some users, and like client B for others. Or based on time of day. So you probably don't want the feature to be responsible for knowing which options it should apply. The code to decide that should be outside the feature, and pass the options in to the feature.

So:

  var customerOptions={
  surfaceConsistencyPreference : 'springy',
  staleDataSweepingPolicy      : 'draconian'
  }


//...in some other file
function someFeature(surfaceConsistencyPreference) {
  // common code

  if(surfaceConsistencyPreference === 'mushy') {
  // does something
  } else if(surfaceConsistencyPreference === 'springy') {
   // does something else
  } // else if ... for every other custom behaviour, or on other parts of the feature

  // common code
}

The general principal is to keep a function dependent on as little else as possible. The someFeature function has no need to be dependent on a clientID - it probably shouldn't know you have clients. It does need to know which custom behavior to use. So pass that in and it won't have to worry about it, and you won't subtly have knowledge of how custom behaviors are determined replicated all over your application in a way that makes it very difficult to modify.

How you go from a customer configuration file to passing the value to a feature is too application specific for me to have anything else to tell you.

Related Topic