Recommended way to support multiple APIs from one codebase

I am the author of the ffi-rzmq gem. It wraps the 0mq [1] (libzmq)
library for use by all of the major Ruby runtimes.

Recently, the 0mq project developers used their 2.x branch to create a
3.x and a 4.x branch. These new branches are changing the API of the
library; it will not be backward compatible with the version 2.x API.

I would like to continue shipping a single gem that can support libzmq
2.x, 3.x and 4.x out of the box without requiring the end user to do
anything fancy. The library supports a function call to retrieve major,
minor and patch version information, so I know I can detect the correct
version at runtime and load the appropriate code.

My question stems from one of organization. The 3.x and 4.x APIs share
many of the same characteristics as the 2.x API, so I’d like to be able
to share that code amongst all of the versions. In the places where they
differ, I don’t know if I should do an if/else kind of structure within
the class definition, subclass it from another file and make the
changes, make the sharable pieces into a module that I can include, use
if/else to require entire files, etc. I have so many choices for how
to approach this that I’m frozen.

The approach I am leaning towards right now requires the creation of a
base class that conforms to the version 2.x API. The 3.x and 4.x APIs
will reopen it and override the appropriate methods to inject the
correct method signature and behavior. So, as the gem loads it will
always load the version2 API and then as it detects either version 3 or
4 it will require another file that essentially reopens the class and
overwrites the appropriate methods.

How do other gem authors solve this issue? Can anyone point to a gem (or
2 or 3) that currently handles this problem?

Thank you…

cr

[1] http://zeromq.org

On 09/13/2011 11:41 PM, Chuck R. wrote:

How do other gem authors solve this issue? Can anyone point to a gem (or 2 or 3)
that currently handles this problem?

Thank you…

cr

[1] http://zeromq.org

Sorry dont have a gem at hand which does it (but i guess many if not
most use this technic), but in principle your thinking was correct.

I’d just use modules instead of directly changing the base class,
a search for “ruby included hook” will show the implementation.

Have fun,
Chris

PS. Modules can be ‘stacked’, so put the basic functionality into say
api_abc_common.rb and version specifics into api_abc_v1.rb and
api_abc_v2.rb. By including api_common from inside api_v1/api_v2, ruby
will call the methods in order, so there is no need for if/else (except
in the part which detects the version to mixin, but using a good naming
scheme for dynamic ‘module-name-generation’ can remove these, too.

On Tue, Sep 13, 2011 at 11:41 PM, Chuck R. [email protected]
wrote:

I am the author of the ffi-rzmq gem. It wraps the 0mq [1] (libzmq) library for
use by all of the major Ruby runtimes.

Recently, the 0mq project developers used their 2.x branch to create a 3.x and a
4.x branch. These new branches are changing the API of the library; it will not
be backward compatible with the version 2.x API.

I would like to continue shipping a single gem that can support libzmq 2.x, 3.x
and 4.x out of the box without requiring the end user to do anything fancy. The
library supports a function call to retrieve major, minor and patch version
information, so I know I can detect the correct version at runtime and load the
appropriate code.

My question stems from one of organization. The 3.x and 4.x APIs share many of
the same characteristics as the 2.x API, so I’d like to be able to share that code
amongst all of the versions. In the places where they differ, I don’t know if I
should do an if/else kind of structure within the class definition, subclass it
from another file and make the changes, make the sharable pieces into a module
that I can include, use if/else to require entire files, etc. I have so many
choices for how to approach this that I’m frozen.

The approach I am leaning towards right now requires the creation of a base
class that conforms to the version 2.x API. The 3.x and 4.x APIs will reopen it
and override the appropriate methods to inject the correct method signature and
behavior. So, as the gem loads it will always load the version2 API and then as it
detects either version 3 or 4 it will require another file that essentially
reopens the class and overwrites the appropriate methods.

This approach won’t work if there can be two versions at the same
time. Even if that could never happen at runtime it might be good
during testing. Generally I would favor an approach where classes are
not reopened. I think this will also make the code easier to read. I
can think of various approaches depending on when you can determine
the library version (i.e. at load time of the gem, or whenever a new
root object from the lib is needed).

I don’t know the current API so I am inventing

module RZMG
class CommonForAll
end

def self.create(*a)
FACTORY.new(*a)
end

determine at load time

FACTORY = case rzmg_version
when /^2/
require ‘rzmg/v2’ # loads RZMG::V2 module etc.
V2::Factory
when /^3/
require ‘rzmg/v3’ # as above
V3::Factory
end
end

If you want to detect versions at usage time you could use autoload to
make initial loading quicker.

Kind regards

robert

On Sep 14, 2011, at 5:54 AM, Robert K. wrote:

This approach won’t work if there can be two versions at the same
time. Even if that could never happen at runtime it might be good
during testing. Generally I would favor an approach where classes are
not reopened. I think this will also make the code easier to read. I
can think of various approaches depending on when you can determine
the library version (i.e. at load time of the gem, or whenever a new
root object from the lib is needed).

I like the suggestion of using modules and "include"ing them based upon
a runtime check.

Thanks to you and Christian for the feedback. I appreciate it.

cr