Interface segregation principle for a framework interface with optional features

Viewed 120

I am designing a authentication framework. I need users of the framework to implement data access logic since it is not the main purpose of the framework and I want to allow multiple data access approaches (SQL, NoSQL, Cache etc.) I don't implement it inside my framework. My framework uses this logic through an interface called IUserStore but the problem is, there are certain methods inside my interface that are used only when certain feature is active and not used otherwise. For example framework will try to access two factor information through GetTwoFactorInfo method only if two factor authentication is enabled.

My question is about interface segregation principle. Is it ok to leave the interface as it is and explain in the documentation that user needs to implement GetTwoFactorInfo only if user wants to use two factor authentication and throw NotImplementedException otherwise? Or should I separate interface for each optional feature and explain in the documentation user should implement and register this interface to service provider to be able to use that feature? A problem with second approach is when injecting services that implement those interfaces to constructors, I need to check if those features are active otherwise I would get an error because service is not registered and I am trying to access those services from service provider. Which leads to extra complexity for my framework classes.

What would be the best way to handle this problem?

1 Answers

There are practical problems with both of the approaches you suggest, but the plan to have clients throw NotImplementedException is far worse.

Let's go through both of them:

Option 1

leave the interface as it is and explain in the documentation that user needs to implement GetTwoFactorInfo only if user wants to use two factor authentication and throw NotImplementedException otherwise

Well, this might work for the problem you have today, but software design is about the problems you'll have tomorrow. What happens if you add support for different authentication methods to future versions of the framework? If you follow this pattern, then you'll add new methods to IUserStore... but this would break existing clients, because they will not have implemented them!

You can get around this particular problem in some languages by providing default implementations for new methods that throw exceptions, but that defeats much of the purpose of defining an interface in the first place -- the type system no longer tells the client what he has to implement.

Also, this pattern only works for pre-existing interfaces. If you add a new authentication method that requires the client to implement a new interface, that you're back to considering something like your second option, and then you'll have an inconsistent mix if versioning strategies. Ick.

Option 2

separate interface for each optional feature and explain in the documentation user should implement and register this interface to service provider to be able to use that feature

This is much better, but also not great, because it introduces hidden rules that clients of your framework have to follow. All of the ways to find out about these rules are frustrating -- read docs, troubleshoot errors, etc.

This is a common problem in lots of dependency injection systems, though, and lots of people don't seem to mind, but things get really complicated as interacting system of hidden rules accumulates.

Option 3

I don't know how you enable this 2-factor feature right now, but I would suggest that you have your customers enable this feature by calling a method that takes the implied dependencies as arguments, like

void enable2FactorAuth(I2FactorInfoStore store)

Then all the hidden rules go away. Your system ensures that you can't enable the feature unless you've implemented the required interfaces. Easy.

If you are thinking that you will lose the ability to configure your product without programming, then I would like to remind you that you do not have that feature. As you said, there is code that clients have to write in order to use 2 factor authentication. They have to implement the store. Requiring them to call a method to enable it will only improve this code, because it will now be obvious why they had to implement that store in the first place.

Related