The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

API design question: Tunnels vs Formal Functions

We are designing a new API at work with two goals (so far as I understand it):

1) Design an API that abstracts away implementation differences between multiple resources (imagine a resource as a Dialog window under Windows vs Linux vs Mac, etc) that allows clients to swap out implementations under the hood without changing the application code.

2) Deliver a early prototype to a customer specifically for the use on top of a single resource. We know they'll be working on top of Windows for now but we want to give them this API so that they will be able to migrate to other "resource providers" in the future.

Now, the company's immediate goal is to get something out the door for the customer (#2) but our longer-term goal is to create this API for all customers so that it works across all "resource providers" that my company sells.

So for example, say a customer is currently coding against "Spam filter Lite" which my company sells but in the future they'll want to swap in "Spam filter PRO" without having to change their application code. This is the problem #1 is trying to solve.

I got into an ideological debate about the use of "tunnels" at work. They want to add "parameters" to API functions that will enable users to "tunnel" resource-specific requests to the underlying provider. That is, given LiteProvider and ProProvider which inherit from IProvider they want to define IProvider.feature("specialFeature1=true") instead of defining ProProvider.specialFeature1(true).

Now, I am personally strongly against this suggestion because I feel it encourages runtime failures as opposed to compile-time features but they argue that it is likely that specialFeature1() will never be available to all IProvider implementations so they'd rather "phase it out" of the API by ignoring the parameter in the future then deprecate a formal API function. My personal suggestion is to give the client direct access to ProProvider.specialFeature1(true) such that if "ProProvider" is phased out in the future the customer will get compile-time errors if he depends on this removed feature *and* even before this feature is phased out it'll be explicit that his code is not portable across all IProviders.

To murky the water further, my co-workers point out that the IProvider implementation sends messages over a standardized communication protocol which essentially does the same: instead of defining new operations in the protocol everyone tunnels special features over parameters.

What do you think?

Thank you,
Gili
Gili Tzabari Send private email
Friday, April 13, 2007
 
 
To clarify:

The company plans on phasing out the "resource provider" they are selling the company in #2 as soon as possible. They want that provider to be supported by their Unified API but they know ahead of time that they will be phasing out its provider-specific features.

Gili
Gili Tzabari Send private email
Friday, April 13, 2007
 
 
Both approaches appear to violate the Open-Closed Principle: "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification."

If you could move each feature into its own entity (such as DLLs), then you would only need one API to "use" a feature entity.
ocp
Friday, April 13, 2007
 
 
ocp,

I'm not sure I understand what you mean. Do the approaches violate the Open-Closed Principle simply because in either case we are removing functionality from a published API? What is one supposed to do when if you need to expose implementation-specific features and eventually an implementation is phased out of the company?

I didn't understand your point about DLLs either, can you please rephase what you meant?

Thanks,
Gili
Gili Tzabari Send private email
Friday, April 13, 2007
 
 
Basically I'm talking about using plugins. If a feature you want to remove could be encapsulated in a plugin, then when the time comes you wouldn't have to change any code or the published API to remove it.
ocp
Friday, April 13, 2007
 
 
This is a very common problem with a fairly standard solution - although it's surprising how many shops have never come across it.

First of all, as I think you've all concluded, you need a standard interface that encapsulates the standard functions that all the platforms will share. This one shouldn't include any 'special' functions for just one platform.

After that, you've basically got two choices. There's a simple one, and a more complex one.

a) Define a fuller interface for each specific platform. This can inherit from the generic interface, but include the more specific functions supported for the specific platform.

You then need some way to disambiguate the interface, and return a pointer to the more specific functions. One way is to define a set of functions

PlatformAIf *IsPlatformA();
PlatformBId *IsPlatformB();

etc. to the generic interface.

Essentially you get a null returned if it isn't that type of platform, or the required pointer if it is. You can then use the pointer if you get it.


b) If you've got a lot of platforms, or you expect complicated patterns of overlapping capability, you need to have a system where each plugin supports LOTS of interfaces for all the different methods it uses. And you query the plugin to ask which interfaces it supports.

Define a QueryInterface function, and an enumeration system for your interfaces. If the plugin supports the interface, it returns it in response to the call.

This way you can always add more interfaces as you need them.
Duncan Sharpe
Saturday, April 14, 2007
 
 
How about #ifdef, like

interface IPlatformSpecific
{
#ifdef IS_PLATFORM_A
 void foo();
#endif
 ... etc ..
}
Christopher Wells Send private email
Saturday, April 14, 2007
 
 
Judging by the '.' for type-level scope, the language is Java?

Anyway, if you want the provider to change without changing the code, you're not going to get much help from the language (whichever it is). If you really want to provide a consistent interface that works regardless of the derived type then you're by definition restricted to using only methods defined in the base class.

So if you want that, AND you want certain derived classes to provide functionality that other derived classes don't, you're a bit stuck. You'll have to use some kind of ad-hoc convention. Whilst I'd be wary of the "tunneling" approach you describe, for exactly the reasons you describe, it's as good an approach as any.
Tom_
Saturday, April 14, 2007
 
 
Don't people usually just define a loose and sloppy interface that almost comes down to a "web service like" contract?

I'm picturing the "server" component accepting a text blob and returning exactly one blob.  The blob could be an XML payload or some other structured text.  Could even be a binary structure instead of text.

A few standard requests from client to server would return "schema" information describing available methods, how to make the calls, and the in and out parameters.  Once the client has those the requests can begin.

It all seems a bit OCD with all of the parsing and generating of course.  The assumption is you're trading off flexibility for performance and know where the sweet spot is.
Codger
Saturday, April 14, 2007
 
 
If I understand you correctly, you want to give the users APIs that independent with the certain platform but you still want to allow the users access special features of the certain platform.

The solution I can think are two approaches,

1, Wrap the platform dependent APIs to some wrapper such as a single class. So your major APIs are still independent. The users have to use that wrap class to access the special features. And when you want to remove those features, just remove the class and force the user to change their code.

2, Use some feature query methods.
For example, if I want to access feature A, I have to write code like
if(SupportFeature("A")) {
    DoFeature("A");
}
In this case, the users don't need to change the code after you remove the features, but it makes much more complex to write such kind of code.
Koms Bomb Send private email
Thursday, April 19, 2007
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz