Monday, June 27, 2011

Refactoring to functional ActionResults in Figment

This is a refactoring tale about Figment, the web framework I've been writing. As with most refactoring tales, I'll be extra verbose and explicit and maybe a little dramatic about each step and its rationale.

Figment is, as I explained when I first wrote about it, based on ASP.NET MVC. As such, it uses many ASP.NET MVC types, like ControllerContext and ActionResult.

Let's say we wanted to create a new ActionResult to model a HTTP "method not allowed" response. The RFC says that this response has a status code 405 and "the response MUST include an Allow header containing a list of valid methods for the requested resource".

Such a response is appropriate when the client issues a request with a HTTP method that is not supported by the server, i.e. not every application can handle a DELETE method. But pretty much every application can handle GET and POST, so when receiving a DELETE request the server could respond with 405 and an Allow: GET, POST header (supporting HEAD is pretty common too, but for this post let's assume only GET and POST are supported).

If we were working with objects and classes we would write a class inheriting ActionResult to encapsulate this, something like (using F#):

type MethodNotAllowed(validMethods: string seq) = 
    inherit ActionResult() with 
        override x.ExecuteResult ctx = 
            ctx.Response.StatusCode <- 405 
            ctx.Response.AppendHeader("Allow", String.Join(", ", validMethods))

and we would use this in Figment like this:

action (ifMethodIs "DELETE") (fun _ -> MethodNotAllowed ["GET"; "POST"])

Thanks to object expressions in F#, we can get away without writing an explicit class:

let methodNotAllowed(validMethods: #seq<string>) = 
    { new ActionResult() with 
        override x.ExecuteResult ctx = 
            ctx.Response.StatusCode <- 405 
            ctx.Response.AppendHeader("Allow", String.Join(", ", validMethods)) }

Now in Figment there's not much difference:

action (ifMethodIs "DELETE") (fun _ -> methodNotAllowed ["GET"; "POST"])

But we can be more concise and functional in the definition of this response.

The first thing to realize is that ActionResult has a single method ExecuteResult with signature ControllerContext -> unit. So we could easily represent it as a regular function ControllerContext -> unit and then build the actual ActionResult whenever we need it:

let inline result r = 
    {new ActionResult() with 
        override x.ExecuteResult ctx = 
            r ctx }

result here is (ControllerContext -> unit) -> ActionResult

This is a pretty common pattern in F# to "functionalize" a single-method interface or class.

Let's also write a little function to execute an ActionResult:

let inline exec ctx (r: ActionResult) = 
    r.ExecuteResult ctx

Setting a status code and header are pretty common things to do. We should encapsulate them into their own ActionResults, and then we can compose them:

let status code = 
    result (fun ctx -> ctx.Response.StatusCode <- code) 

let header name value = 
    result (fun ctx -> ctx.Response.AppendHeader(name, value))

Now we'd like to define methodNotAllowed by composing these two ActionResults, for example:

let allow (methods: #seq<string>) = header "Allow" (String.Join(", ", methods))

let methodNotAllowed methods = status 405 >>. allow methods

Notice how the ControllerContext and Response are implicit now. We can define the >>. operator  like this:

let concat a b = 
    let c ctx = 
        exec ctx a 
        exec ctx b 
    result c

let (>>.) = concat 

That is, concat executes two ActionResults sequentially.

But wait a minute... "sequencing actions"... where have we seen this before? Yup, monads. We have just reinvented the Reader monad. Let's not reinvent it and instead make it explicit, but first we have to refactor Figment to use ControllerContext -> unit instead of ActionResult (it will be still used, but under the covers). This is a simple, mechanical, uninteresting refactor, so I won't show it. Just consider it done. Now we can define:

type ReaderBuilder() =
    member x.Bind(m, f) = fun c -> f (m c) c
    member x.Return a = fun _ -> a
    member x.ReturnFrom a = a

let result = ReaderBuilder()
let (>>.) m f = r.Bind(m, fun _ -> f)
let (>>=) m f = r.Bind(m,f)

>>. is just like Haskell's >> operator, described like this:

Sequentially compose two actions, discarding any value produced by the first, like sequencing operators (such as the semicolon) in imperative languages.

We can still define methodNotAllowed as above, or using computation expression syntax:

let methodNotAllowed methods = 
    result { 
        do! status 405 
        do! allow methods 
    }

Or, if you don't want to use monads, you can just pass the context explicitly:

let methodNotAllowed allowedMethods = 
    fun ctx -> 
        status 405 ctx 
        allow allowedMethods ctx

They're all equivalent definitions.

Now, after the last refactor ("unpacking" ActionResult) we changed the Figment action type from

ControllerContext -> ActionResult

to

ControllerContext -> (ControllerContext -> unit)

which is a pretty silly type if you think about it... but that's a refactoring tale for another day ;-)

I hope this post served to contrast object-oriented and functional code in a familiar environment (ASP.NET MVC), and to show how monads can arise "naturally", it's just a matter of recognizing them.

I should say that this is of course not the only way to do it, and I don't claim this is the best way to do it.

In the next post I'll show more uses and conveniences of having the action as a Reader monad.

Sunday, June 19, 2011

SolrNet 0.4.0 alpha 1 released

SolrNet is a Solr client for .NET, and I just released version 0.4.0 alpha 1.

Before detailing the changes, I'd like to clarify what alpha means to me:

  • Core functionality is stable (otherwise I wouldn't even bother releasing)
  • New features are unit-tested (otherwise I wouldn't even admit them into the repo), but may not be battle-tested
  • Not feature-frozen (i.e. future alphas/betas might get new features)
  • Not interface-frozen (I make no guarantees about breaking changes between this alpha and the final release)
  • Little to no documentation about new features or changes. Tests are the primary or maybe only source of usage.

The goal of this release is to get real-world testing and feedback on the new features and changes.

New features

  • Solr 4.0 grouping (this used to be called "field collapsing", but was completely overhauled)
  • Solr 4.0 pivot faceting
  • Autofac integration module
  • Unity integration
  • Fluent interface: added index-time document boosting
  • Fluent interface: added easier way to set Solr URL and timeout
  • SolrQueryByDistance (spatial search)
  • Support for ExtractingRequestHandler (i.e. binary/proprietary document indexing, like MS Word, PDF, etc)
  • Rollback (it was implemented but missing in ISolrOperations)
  • CommitWithin and Overwrite parameters for document add
  • Mixed exclusive/inclusive range queries (Solr 4.0 only)

Bugfixes

  • Fixed support for nullable enum properties with empty Solr field value.

Breaking changes

  • Breaking change for IReadOnlyMappingManager implementors: it now indexes fields by name to speed up lookup.
  • Breaking change: SolrQueryByField now quotes '*' and '?'
  • Minor breaking change for direct users of SolrConnection: removed constructor with IHttpWebRequestFactory parameter and made it a property.

Other stuff

  • Upgraded to Windsor 2.5.3
  • Upgraded to Ninject 2.2.1.0
  • Upgraded to NHibernate 3.1.0
  • Upgraded to StructureMap 2.6.2
  • Upgraded to .NET 3.5

Contributors

I'm very happy to say that the project is getting more and more contributors all the time, and they're doing a great job! Here are the contributors to this release:

A huge thank you to them all!

Binaries available on google code.

Tuesday, June 14, 2011

A HTTP content negotiation library in F#

I've been writing a HTTP content negotiation (server-driven) library in F# I called FsConneg (yeah, I've been pretty lazy lately about naming things).

First, here's a little introduction to the topic:

Content negotiation is briefly specified in section 12 of RFC2616, although the meat of it is really in the definitions of the Accept-* headers. There are four content characteristics that can be negotiated: media type, language, charset and encoding.

Encoding refers to content compression, and is usually handled at the web server level, for example IIS static/dynamic compression or mod_deflate in Apache.

Charset refers to UTF-8, ISO-8859-1, etc. The most interesting are media type and language which are the most commonly negotiated characteristics in user-level code. Language is self-explanatory, and media type negotiates whether the response should be XML, JSON, PDF, HTML, etc.

FsConneg is inspired by clj-conneg and has a similar interface. clj-conneg currently negotiates media types only, but FsConneg can negotiate any content characteristic. Like clj-conneg, FsConneg doesn't assume any particular web framework, it works with raw header values, and so it can be easily integrated into any web server or framework.

Let's say you have a server application that can respond with application/xml or application/json, but it prefers application/json:

let serves = ["application/json"; "application/xml"]

And you get a request from a user agent with an Accept header looking like this:

let accepts = "text/html, application/xml;q=0.8, */*;q=0.5"

Which means: Hey server, I prefer a text/html response, but if you can't do that I'll take application/xml, or as a last resort give me whatever media type you have.

Given these two constraints, the server wants to find out what media type it should use:

match bestMediaType serves accepts with
| Some (mediaType, q) -> printfn "Negotiated media type %s, now the server formats its response with this media type" mediaType
| _ -> failwithf "Couldn't negotiate an acceptable media type with client: %s" accepts

In this example, the negotiated media type is of course application/xml. In case of negotiation failure, the server should respond with status 406 Not Acceptable.

There are similar functions bestEncoding, bestCharset and bestLanguage to negotiate the other content characteristics.

At a lower level, you might want to use negotiate* functions. Instead of giving you a single best type, these give you a sorted list of acceptable types. For example, using the same serves and accepts as above:

> negotiateMediaType serves accepts

val it : (string * float) list =
  [("application/xml", 0.8); ("application/json", 0.5)]

Even though server-driven content negotiation was defined back in 1997, it hasn't been used a lot, and with good reason. Every party involved (user agent, server and proxies) has to implement negotiation semantics right, or Bad Things could happen, like the user agent asking for a page in English and getting it in French because some proxy didn't honor the Vary header.
Until a few years ago, Internet Explorer didn't handle the Vary header all too well, and some proxies had issues as well. Until version 9, Internet Explorer used to send a mess of an Accept header, and WebKit preferred application/xml over text/html, which doesn't make much sense for a browser. Here's a spreadsheet with the Accept header some browsers send. Also, we developers and the frameworks we use sometimes get details wrong. Pure server-driven language negotiation is most of the time insufficient and has to be complemented with agent-driven negotiation. Even Roy Fielding, who defined REST, says that "a server is rarely able to make effective use of preemptive negotiation".
As a server app developer, some of these issues are out of your control, yet affect how content gets served to your clients. Many people argue that content negotiation is broken, or overly complex, or an ivory tower concept, or just don't agree with it.

I think it still can work and has its time and place, in particular for APIs. But just like the rest of REST (no pun intended), content negotiation is not as simple as it might seem at first sight.

In the next post I'll describe some ways to do concrete content negotiation with this library and Figment.

The code for FsConneg is on github.