FAQ
I'm looking for suggestions how to best support multiple API versions
in an application.

The API and web share many controller actions. As is not uncommon,
actions available for the API are defined with an attribute:

sub foo : Local XMLRPC( 'foo.get' ) {

This is great for sharing code as often the API just exposes
the same functionality as the web interface.


When a new version of the web application is released then all web
users see the new version at the same time. If an action in the new
version expects an additional new parameter then the form posting to
that action is modified at the same time.

But, the API access to an application typically needs to be backward
compatible to allow API users time to update their client applications
with the newer requirements.

So, it seems I would need multiple controller actions that are
dispatched based on some version.


Here's one idea I was kicking around:

Say I have an existing controller action that is used by the web
users, but also available as an XMLRPC API method:

sub widget : Local XMLRPC( 'widget.get' ) {

So in a new application version that controller action is changed
and now requires a new parameter and returns new data.

In the new version I want to support the new action but remain
backward compatible.

# fetch widget for web and API
sub widget : Local XMLRPC( 'widget.get' ) Version( 2 ) {

# deprecated to support old version of API
sub widget_old : XMLRPC( 'widget.get' ) Version( 1 ) {

Then in my custom API dispatcher match method I take the version into
consideration when matching actions.


Any better suggestions?



--
Bill Moseley
moseley@hank.org
Sent from my iMutt

Search Discussions

  • Markus Holzer at Jul 23, 2008 at 12:54 am

    Bill Moseley schrieb:
    I'm looking for suggestions how to best support multiple API versions
    in an application.

    The API and web share many controller actions. As is not uncommon,
    actions available for the API are defined with an attribute:

    sub foo : Local XMLRPC( 'foo.get' ) {

    This is great for sharing code as often the API just exposes
    the same functionality as the web interface.


    When a new version of the web application is released then all web
    users see the new version at the same time. If an action in the new
    version expects an additional new parameter then the form posting to
    that action is modified at the same time.

    But, the API access to an application typically needs to be backward
    compatible to allow API users time to update their client applications
    with the newer requirements.

    So, it seems I would need multiple controller actions that are
    dispatched based on some version.


    Here's one idea I was kicking around:

    Say I have an existing controller action that is used by the web
    users, but also available as an XMLRPC API method:

    sub widget : Local XMLRPC( 'widget.get' ) {

    So in a new application version that controller action is changed
    and now requires a new parameter and returns new data.

    In the new version I want to support the new action but remain
    backward compatible.

    # fetch widget for web and API
    sub widget : Local XMLRPC( 'widget.get' ) Version( 2 ) {

    # deprecated to support old version of API
    sub widget_old : XMLRPC( 'widget.get' ) Version( 1 ) {

    Then in my custom API dispatcher match method I take the version into
    consideration when matching actions.


    Any better suggestions?


    You could make the version of the api being used a parameter of the
    request, falling back to the latest stable version if not present. Then,
    in your controller you use a simple dispatch table to call the function
    that should the lifting.
  • Matt S Trout at Jul 26, 2008 at 6:00 am

    On Fri, Jul 25, 2008 at 07:09:58AM -0700, Bill Moseley wrote:
    I'm looking for suggestions how to best support multiple API versions
    in an application.

    The API and web share many controller actions. As is not uncommon,
    actions available for the API are defined with an attribute:

    sub foo : Local XMLRPC( 'foo.get' ) {

    This is great for sharing code as often the API just exposes
    the same functionality as the web interface.


    When a new version of the web application is released then all web
    users see the new version at the same time. If an action in the new
    version expects an additional new parameter then the form posting to
    that action is modified at the same time.

    But, the API access to an application typically needs to be backward
    compatible to allow API users time to update their client applications
    with the newer requirements.

    So, it seems I would need multiple controller actions that are
    dispatched based on some version.


    Here's one idea I was kicking around:

    Say I have an existing controller action that is used by the web
    users, but also available as an XMLRPC API method:

    sub widget : Local XMLRPC( 'widget.get' ) {

    So in a new application version that controller action is changed
    and now requires a new parameter and returns new data.

    In the new version I want to support the new action but remain
    backward compatible.

    # fetch widget for web and API
    sub widget : Local XMLRPC( 'widget.get' ) Version( 2 ) {

    # deprecated to support old version of API
    sub widget_old : XMLRPC( 'widget.get' ) Version( 1 ) {
    sub widget :Local VersionedXMLRPC('widget.get') {

    sub widget_xmlrpc_v1 {

    have VersionedXMLRPC apply a custom a ction class that does ->can
    based dispatch, same way Catalyst::Action::REST does.

    Could even make XMLRPC always do that in your app, you'll need
    a controller base class to provide the _parse_AttrName method
    iether way.

    Seems like a bit less typing without any real loss of precision
    - the main action should likely fire for any version not explicitly
    handled for compat so no version given or a compatible newer version
    just happen.

    If you're going to assume integer versions (or some method name valid
    encoding of a more complex version number) you could probably instead
    have the action class scan the controller's entire namespace on
    creation with a view to making _v9 handle anything below v9 as well
    until it finds a more specific, lower versioned compat handler (_v3
    or similar).

    --
    Matt S Trout Need help with your Catalyst or DBIx::Class project?
    Technical Director http://www.shadowcat.co.uk/catalyst/
    Shadowcat Systems Ltd. Want a managed development or deployment platform?
    http://chainsawblues.vox.com/ http://www.shadowcat.co.uk/servers/
  • Bill Moseley at Jul 27, 2008 at 5:09 pm

    On Sat, Jul 26, 2008 at 06:00:39AM +0100, Matt S Trout wrote:
    sub widget :Local VersionedXMLRPC('widget.get') {

    sub widget_xmlrpc_v1 {

    have VersionedXMLRPC apply a custom a ction class that does ->can
    based dispatch, same way Catalyst::Action::REST does.
    C::Action::REST uses "ActionClass('REST')" to specify the class for
    the action. And with a custom request class, has a custom dispatcher to
    dispatch based on the request method.

    Your example above does not use ActionClass. Were you suggesting that
    these XMLRPC actions have their own action class, and if so how would
    the actions be setup then?

    I would think the Catalyst approach would be something like this:

    sub widget : Local ActionClass('XMLRPC', 'widget.get' ) {


    There's more than one approach, of course.

    My current approach (w/o versioning) is to have a custom dispatcher
    type (which I push onto $c->dispatcher->preload_dispatch_types). I
    also have a custom HTTP::Body type to parse the XMLRPC payload. Once
    the XMLRPC method name is known from the request the dispatcher
    searches for the matching action.

    But, I do like the approach of matching the action, and then using
    $controller->can to try and find an appropriate version as you
    suggested.

    By the way, my assumption is I would have the entire XMLRPC API
    versioned. I asked about this on the XMLRPC list and it was
    recommended that instead I version individual methods. That is, have
    separate method names that include a version:

    widget.1.get
    widget.2.get
    etc.

    which would make the Catalyst part very simple, but I'm not sure I
    like that idea of each method having a version in the method name.

    --
    Bill Moseley
    moseley@hank.org
    Sent from my iMutt
  • Wade Stuart at Jul 28, 2008 at 12:42 am

    Bill Moseley wrote on 07/27/2008 11:09:46 AM:
    On Sat, Jul 26, 2008 at 06:00:39AM +0100, Matt S Trout wrote:

    sub widget :Local VersionedXMLRPC('widget.get') {

    sub widget_xmlrpc_v1 {

    have VersionedXMLRPC apply a custom a ction class that does ->can
    based dispatch, same way Catalyst::Action::REST does.
    C::Action::REST uses "ActionClass('REST')" to specify the class for
    the action. And with a custom request class, has a custom dispatcher to
    dispatch based on the request method.

    Your example above does not use ActionClass. Were you suggesting that
    these XMLRPC actions have their own action class, and if so how would
    the actions be setup then?

    I would think the Catalyst approach would be something like this:

    sub widget : Local ActionClass('XMLRPC', 'widget.get' ) {


    There's more than one approach, of course.

    My current approach (w/o versioning) is to have a custom dispatcher
    type (which I push onto $c->dispatcher->preload_dispatch_types). I
    also have a custom HTTP::Body type to parse the XMLRPC payload. Once
    the XMLRPC method name is known from the request the dispatcher
    searches for the matching action.

    But, I do like the approach of matching the action, and then using
    $controller->can to try and find an appropriate version as you
    suggested.

    By the way, my assumption is I would have the entire XMLRPC API
    versioned. I asked about this on the XMLRPC list and it was
    recommended that instead I version individual methods. That is, have
    separate method names that include a version:

    widget.1.get
    widget.2.get
    etc.

    which would make the Catalyst part very simple, but I'm not sure I
    like that idea of each method having a version in the method name.
    Bill,

    Icky, I think the API should be versioned -- not methods. What if
    the methods across versions are not compatible (widget1 output used with
    foo2) versioning the api forces all methods to be used with their tested
    and versioned partners. When you have 30 or 40 different revisions and
    developers start relying on mismatching methods from different versions
    that seems like a headache waiting to happen.

    -Wade

    -Wade
  • Bill Moseley at Jul 28, 2008 at 7:53 am

    On Sun, Jul 27, 2008 at 06:42:23PM -0500, Wade.Stuart@fallon.com wrote:
    widget.1.get
    widget.2.get
    etc.
    Icky, I think the API should be versioned -- not methods. What if
    the methods across versions are not compatible (widget1 output used with
    foo2) versioning the api forces all methods to be used with their tested
    and versioned partners. When you have 30 or 40 different revisions and
    developers start relying on mismatching methods from different versions
    that seems like a headache waiting to happen.
    I completely agree.

    I suppose a "version" XMLRPC parameter in the request payload is
    possible, but I'm actually leaning more toward just using separate
    endpoints:

    http://localhost:3000/rpc1.2
    http://localhost:3000/rpc1.3

    or

    http://localhost:3000/rpc/1.2
    http://localhost:3000/rpc/1.3

    or

    http://localhost:3000/rpc?version=1.2

    --
    Bill Moseley
    moseley@hank.org
    Sent from my iMutt
  • Wade Stuart at Jul 28, 2008 at 4:49 pm

    Bill Moseley wrote on 07/28/2008 01:53:11 AM:
    On Sun, Jul 27, 2008 at 06:42:23PM -0500, Wade.Stuart@fallon.com wrote:
    widget.1.get
    widget.2.get
    etc.
    Icky, I think the API should be versioned -- not methods. What
    if
    the methods across versions are not compatible (widget1 output used
    with
    foo2) versioning the api forces all methods to be used with their
    tested
    and versioned partners. When you have 30 or 40 different revisions and
    developers start relying on mismatching methods from different versions
    that seems like a headache waiting to happen.
    I completely agree.

    I suppose a "version" XMLRPC parameter in the request payload is
    possible, but I'm actually leaning more toward just using separate
    endpoints:

    http://localhost:3000/rpc1.2
    http://localhost:3000/rpc1.3

    or

    http://localhost:3000/rpc/1.2
    http://localhost:3000/rpc/1.3

    or

    http://localhost:3000/rpc?version=1.2
    I guess there would be nothing wrong with:

    api10.example.com:3000/method
    api11.example.com:3000/method

    either, I guess it depends on if users are going to be using it or if it
    is all app usage. if all app usage that would allow you to just rev the
    api in source control / separate procs and not have a mammoth app with x
    number of api versions hanging out and possibly conflicting.
  • Matt S Trout at Jul 28, 2008 at 9:43 pm

    On Sun, Jul 27, 2008 at 09:09:46AM -0700, Bill Moseley wrote:
    On Sat, Jul 26, 2008 at 06:00:39AM +0100, Matt S Trout wrote:

    sub widget :Local VersionedXMLRPC('widget.get') {

    sub widget_xmlrpc_v1 {

    have VersionedXMLRPC apply a custom a ction class that does ->can
    based dispatch, same way Catalyst::Action::REST does.
    C::Action::REST uses "ActionClass('REST')" to specify the class for
    the action. And with a custom request class, has a custom dispatcher to
    dispatch based on the request method.

    Your example above does not use ActionClass. Were you suggesting that
    these XMLRPC actions have their own action class, and if so how would
    the actions be setup then?
    My example suggests you write _parse_VersionedXMLRPC_attr to apply
    the action class etc.
    By the way, my assumption is I would have the entire XMLRPC API
    versioned. I asked about this on the XMLRPC list and it was
    recommended that instead I version individual methods. That is, have
    separate method names that include a version:

    widget.1.get
    widget.2.get
    etc.

    which would make the Catalyst part very simple, but I'm not sure I
    like that idea of each method having a version in the method name.
    I'd suggest a header, really. X-MyApp-XMLRPC-Api-Version: or similar.

    Makes the versioning stuff optional, thus simplifying the simple case.

    --
    Matt S Trout Need help with your Catalyst or DBIx::Class project?
    Technical Director http://www.shadowcat.co.uk/catalyst/
    Shadowcat Systems Ltd. Want a managed development or deployment platform?
    http://chainsawblues.vox.com/ http://www.shadowcat.co.uk/servers/

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupcatalyst @
categoriescatalyst, perl
postedJul 23, '08 at 12:54a
activeJul 28, '08 at 9:43p
posts8
users4
websitecatalystframework.org
irc#catalyst

People

Translate

site design / logo © 2021 Grokbase