Thursday, 7 May 2015

Restival Part 3: Hello, World!

So far, we've got a /hello service up and running in four different frameworks (five if you include Andrew 'kolektiv' Cherry's F#/Freya implementation, which looks really interesting). Last time, we looked at how our frameworks handle routing; in this instalment, we're going to look at adding default values to our route parameters. Specifically, if you just call /hello, the service should return "Hello, World!"

GET /hello

should return

200 OK

{ "Message" : "Hello, World!" }

OK, so how we do this? There's three places we can potentially do it - the routing, the handlers, or the underlying Greeting object itself - but not all our frameworks support all three options.

Only WebAPI supports explicit defaults in the routing configuration, using this syntax:

config.Routes.MapHttpRoute(
    "Hello", // route name
    "hello/{name}", // route template
    new { Controller = "Hello", Name = "World" } // defaults
);

ServiceStack doesn't support routing defaults per se, but because the request is decoupled from the rest of our implementation, we can specify the default inside our request DTO - and thanks to the single responsibility principle, we know this isn't going to affect any other details of our implementation

[Route("/hello")]
[Route("/hello/{name}")]
public class Hello {
    private string name = "World";
    public string Name {
        get { return name; }
        set { name = value; }
    }
}

OpenRasta's routing maps our requests directly onto the Get method of our HelloHandler, which makes it really easy to use .NET's optional parameter syntax to supply a default value to the handler method itself:

public class HelloHandler {
    public object Get(string name = "World") {
        return (new Greeting(name));
    }
}

Finally, NancyFX has such sparse routing that there's only really one line of code we could modify - after all, our entire API's only two lines of code, and the other one's doing our /hello/{name} implementation.

public class HelloModule : NancyModule {
    public HelloModule() {
        Get["/hello"] = _ => new Greeting("World");
        Get["/hello/{name}"] = parameters => new Greeting(parameters.name);
    }
}

Astute readers will be going "but hang on - you've just defined the same default in four different places! That's not very SOLID! What if it changes?" - and yes, you're exactly right. Because this is a demo application, I'm favouring readability over abstraction; if you had to define this kind of default behaviour in four different places in a real app, you'd do it using constants, or implement it on the Greeting object itself so the code is reused between all four endpoints.

Code for this post is on GitHub as v0.0.2.

Now we've got a basic skeleton API up and running, it's probably time to share my to-do list - things we'll be implementing, in no particular order

  • List resources, and retrieving multiple objects
  • Support for PUT and DELETE (easy), POST (interesting) and PATCH (really interesting)
  • Hypermedia links using HAL
  • Pagination and resource expansion, as described in this excellent post on the Stormpath blog and this blog post from Etsy's Venkata Mahalingam
  • API versioning - because, sooner or later, we're going to break something and need to maintain backward compatibility for older clients. Check out Troy Hunt's great post on API versioning for a preview of how we'll be getting this wrong in three different ways.
  • OAuth2 and bearer authentication - this one's going to take some work, because before you can build an OAuth2 API, you need an OAuth2 authentication server, but I've wanted to build a really lightweight .NET OAuth2 server for a while so I'm quite looking forward to it.
  • API documentation using tools like Swagger, which generates documentation based on metadata exposed by your API itself.

See you next time.

2 comments:

Mike Thomas said...

Just wondering, but any reason why you are not looking at attribute routing in WebAPI? I find it easier to map routes to classes/methods by using the attributes rather than the older route maps.

Might be useful to discuss this as well if you are comparing the different frameworks.

Dylan Beattie said...

@Mike - good point - check out http://dylanbeattie.blogspot.co.uk/2015/05/restival-part-2-revisited-attribute.html