public
Last active

Bits'bout Play2 Architecture

  • Download Gist
gistfile1.md
Markdown

Bits'bout Play2 Architecture

Play2 Simple HTTP API

Essential blueprint of Play2 architecture is pretty simple and it should be easy to explain in a fairly short blog post. The framework can be understood progressively at different levels; each time having better exposure to some aspects of its design.

The core of Play2 is really small, surrounded by a fair amount of useful APIs, services and structure to make Web Programming tasks easier.

Basically, Play2 is an API that abstractly have the folllowing type

RequestHeader -> Array[Byte] -> Result 

Which is a computation that takes the request header RequestHeader, then takes bytes of the body of the request Array[Byte] and produces a Result.

Now this type presumes putting request body entirely into memory (or disk), even if you only want to compute a value out of it, or better forward it to a storage service like Amazon S3.

We rather want to receive request body chunks as a stream and be able to process them progressively if necessary.

What we need to change is the second arrow to make it receive its input in chunks and eventually produce a result. There is a type that does exactly this, it is called Iteratee and takes two type parameters.

Iteratee[E,R] is a type of arrow that will take its input in chunks of type E and eventually return R. For our API we need an Iteratee that takes chunks of Array[Byte] and eventually return a Result. So we slightly modify the type to be:

RequestHeader -> Iteratee[Array[Byte],Result]

For the first arrow, we are simply using the Function[From,To] type aliased with =>. Actually, and unimportantly, if I define an infix type alias

type ==>[E,R] = Iteratee[E,R] then I can write the type in a funnier way:

RequestHeader => Array[Byte] ==> Result

And this reads: Take the request headers, take chunks of Array[Byte] which represent the request body and eventually return a Result.

The Result type, on the other hand, can be abstractly thought of as the response headers and the body of the response

case class Result(headers: ResponseHeader, body:Array[Byte])

But same here, what if we want to send the response body progressively to the client without filling it entirely into memory. We need to improve our type. We need to replace the body type from an Array[Byte] to something that produces chunks of Array[Byte] in case we need to send the body progressively. Anyway if we don't care then we can still send the entire body as a single chunk.

Such type exists and is called Enumerator[E] which means that it is capable of producing chunks of E, in our case Enumerator[Array[Byte]]

case class Result(headers:ResponseHeaders, body:Enumerator[Array[Byte]])

It is a bit more practical to be able to stream and write to the socket anything that is convertible to an Array[Byte], that is what Writeable[E] insures for a given type 'E':

case class Result[E](headers:ResponseHeaders, body:Enumerator[E])(implicit writeable:Writeable[E])

Bottom Line

The essential Play2 HTTP API is quite simple:

RequestHeader -> Iteratee[Array[Byte],Result]

or the funnier

RequestHeader => Array[Byte] ==> Result

Which reads as the following: Take the RequestHeader then take chunks of Array[Byte] and return a response. A response consists of ResponseHeaders and a body which is chunks of values convertible to Array[Byte] to be written to the socket represented in the Enumerator[E] type.

From this point we can explore how does this simple API behave in the runtime management of Play2 (Threads), what opportunities it opens (NIO, Reactive, Streams, File Upload ...) and how can we easily extend it to model different architectural patterns (Classical MVC, Resource Oriented, ...)

note: use of words computation and arrow is not accidental

note2: the API discussed here is slightly simpler than the current and will appear in 2.1

note2: the API discussed here is slightly simpler than the current and will appear in 2.1

Is there any significant incompatibilities? Will it require changes in app that uses 2.0?

You mention computation and arrow but you don't mention composition. So how exactly does middleware fit into this whole mix? More specifically given A :: RequestHeader => Array[Byte] ===> Result how do I compose it with a function B :: Result -> Result? It can't be simple function composition because of all the fat arrows.

It can actually since you can lift your pure function and compose it with the Iteratee (fat arrow)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.