Extending your API

So, I’m sure we’ve all been there. You have this great API, but now you need to pass some new piece of data from the framework down in to your plugin architecture. Or maybe you haven’t and I’m the only one that ever has this problem. Either way, let’s look at an example of this problem and one way of working around it.

I don’t want to go in to my specific API, but for the sake of conversation, let’s think about a hardware abstraction layer application. Something like the one described in this great Center of Excellence document at NI.com. The architecture they describe looks like this:

Example of NI Hardware and Measurement Abstraction Layer reference architecture

In short, there are two base classes: one for functions and one for drivers, and both support plugins. Plugins of the function class use instances of the driver plugins. Pretty straightforward, and actually seems pretty versatile.

More specifically, as I understand it, the “AcqVoltage” driver plugin would call a “read” method in the base function class, which then somehow calls a read method in the Drv_NI-DMM. Let’s assume that a) that all just works and b) the conn pane for the base “read” looks something like this:

Theoretical base read function

Somehow this ends up invoking the Drv_NI-DMM read, which returns the requested “output” values. And, like any forward thinking person, we left ourselves an extra variant input for future use because let’s face it, you always miss something. Note that this variant is passed all the way down to Drv_NI-DMM read.

Turns out we did. We forgot we need to pass in several key settings, like timeout, resolution and mode (AC or DC). Good thing we left that input. Let’s make a quick typdef that has the info we need and connect it up:

And, on the other end, we would end up with something like this:

And, problem solved! Except…

Our Drv_NI-DMM plugin now has a dependency on the AcqVoltage plugin. What happens when we deploy the driver plugin to a system that doesn’t have the AcqVoltage function? Welcome to the land of broken dreams (and VIs).

Solution 1: Live with it

I refuse.

Solution 2: No typedef

Pretty obvious solution, but let’s look at a key flaw:

Let’s say you need to add element later for another setting that adds accuracy (maybe aperture time), but isn’t strictly required by the DMM driver. Moving forward, we definitely want to implement it in new plugins, but we’re actually not too upset if our existing plugins just ignore it.

Unfortunately, if you add it on the function side, but not on the driver side, once again, we’re broken.

Solution 3: Make a “common” library

This isn’t unusual, and I’ve done it myself. Just make a “common” library that has a bunch of typdefs that you use on your API boundaries. This does have the advantage of not coupling your plugins to each other directly, but it does still add another dependency that always needs to be included. In my experience, common libraries tend to grow rapidly, and are usually a code smell if just used for typedefs.

So, we’re not broken, but the solution stinks.

Solution 4: Use the variant’s attributes

Forget the concept of converting data to a variant. Just use the attributes:

Now you just have to do a “get” on the attributes on the other side, and suddenly, you have all of your data and zero dependencies.

Even better, you can add as many attributes as you want on the function plugin side, and then happily do nothing to your driver plugin and it will just work. New driver plugins, however, still get all the benefits of the new arguments.

The only downsides I’ve found to this so far are that you MUST document what you’re doing for future plugin developers to have any hope of succeeding and that clusters have to be defined as subvariants with attributes of basic data types. That being said, I still think this is the best solution that I’ve found to this problem so far.

Leave a Reply

Your email address will not be published. Required fields are marked *