Bill,
>
Great questions and thoughts, I've inlined comment below. Sorry for the odd
formatting, yahoo email just seems to get worse and worse...
>
>________________________________
>From: Bill Moseley <[email protected]>
>To: The elegant MVC web framework <[email protected]>
>Sent: Tuesday, September 17, 2013 10:52 AM
>Subject: [Catalyst] REST and versioning
>
>
>I've once again used up an hour or so reading Stack Overflow and blog posts on
>REST API versioning. (And the arguments for and against.)
>
>
>Perhaps extending the discussion on how Catalyst supports REST:
>
>
>https://github.com/perl-catalyst/CatalystX-Proposals-REStandContentNegotiation
>
>
>I'm wondering if Catalyst might help in supporting API versions. Somewhat
>similar to how Catalyst::Action::REST will call methods based on the HTTP
>method, perhaps call actions based on some version (provided by some means --
>like a version in the path or in an Accept header).
>
People seem to get religious over how to version their API. I doubt I'd want
to take sides on this but here's how I think we could do both side (URL version
and content type versioning
>
>Catalyst::Action::REST helps keep the actions tidy by calling methods specific
>to each method (foo_GET, foo_PUT). Obviously, we could simply check if (
>$req->method eq 'GET' ) but would end up with pretty ugly actions and no
>automatic "Allow" header.
>
>
>With versions I'm concerned about that my foo_GET method will end up with a
>bunch of "if ( $version > 1.1 ) {....} elsif ($version >1.0 ) ...
>
>
>So, running with the C::Action::REST approach, something like:
>
>
>sub foo_GET { ... } # Default
>sub foo_GET : Version( 1.1 ) { ... } # Use if client requests version is 1.1
>
>
>Frankly, seems like maintenance nightmare and Action explosion. Where that
>version comes from (url, Accpet header) is often debated (see links).
>
>
So the most recent stable Catalyst lets you declare http method matching
natively, so here's how I might do this with Cat out of the box (untested code,
but should serve the idea)
Lets say you want to match a url like /api/$version/...
package Myapp::Web::Controller::API;
use base 'Catalyst::Controller';
sub start : ChainedParent
PathPrefix CaptureArgs(0)
{
my ($self, $ctx) = @_;
}
sub version_one : Chained('start') PathPart('1') Args(0) { ... }
sub version_two : Chained('start') PathPart('2') Args(0) { ... }
1;
package Myapp::Web::Controller::API::1;
use base 'Catalyst::Controller';
sub start : ChainedParent
PathPrefix CaptureArgs(0)
{
my ($self, $ctx) = @_;
}
1;
package Myapp::Web::Controller::API::2;
use base 'Catalyst::Controller';
sub start : ChainedParent
PathPrefix CaptureArgs(0)
{
my ($self, $ctx) = @_;
}
1;
I guess you could use this with Catalyst:Action::REST based controllers as
well. There's probably a few ways you could do this. but I'd probably combine
chaining with different ontrollers for different versions so that I could best
group the common functionality.
If you wanted to take the content negotiation approach, this would fit right
into the proposal
package MyApp::Web::Controller::User; use base 'Catalyst::Controller'; sub
example :Local Provides('application/vnd.mycompany.user.v1+json') { my ($self,
$ctx) = @_; } 1;
In this case the Provides attribute could be setup to match and route as
expected. We might want to consider allowing Regexp or some subset of regexp
so that you could match more than one type of incoming requested response (for
example you might care about the application/vnd.mycompany.user.v1 but not the
JSON bit, and might use some other strategy, as best to avoid repeating
yourself a lot.
>
>
>Any better ideas how to support versioning in Catalyst actions?
>
>
Well, I think the two general approaches are outlined here. some people like
to version as part of the URL, others follow a more purely restful approach and
insist it is a matter for content negotiation. I imagine you could have some
plack middleware to smooth this over, for example to use content accept
introspection in your code, but allow people to have some tag as a query param
or similar. We do this with the HTTP method matching for the newer Catalyst,
since most browsers only support GET and POST method verbs, you can set a
custom http header to map POST to PUT or DELETE. This might be a good approach
when dealing with clients that are not smart about doing true RESTful
negotiation.
It might also be useful to take a look at what some other frameworks in the
RUby and Python world are doing.
>
>
>
>
>The subject of versioning is a bit overwhelming. Here's some starting points,
>if curious:
>
>
> *
>http://stackoverflow.com/questions/389169/best-practices-for-api-versioning
>
>
> * http://www.lexicalscope.com/blog/2012/03/12/how-are-rest-apis-versioned/
>
>
> * http://www.subbu.org/blog/2008/05/avoid-versioning-please
>
>
> *
>http://stackoverflow.com/questions/2024600/rest-api-versioning-only-version-the-representation-not-the-resource-itself?lq=1
>
>
> * http://www.informit.com/articles/article.aspx?p=1566460
>
>
> * http://stackoverflow.com/questions/972226/how-to-version-rest-uris
>
>
> * and plenty more...
>
>
Great links, and covers the based around a lot of these options. Versioning
needs is one reason I tend to not favor JSON for my canonical APIs, since its a
great data format but lacks a bit for robustness as a document exchange format.
But that's another religious argument I guess :)
Thanks!
John
>
>
>--
>Bill Moseley
>[email protected]
>_______________________________________________
>List: [email protected]
>Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
>Searchable archive: http://www.mail-archive.com/[email protected]/
>Dev site: http://dev.catalyst.perl.org/
>
_______________________________________________
List: [email protected]
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/[email protected]/
Dev site: http://dev.catalyst.perl.org/