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])
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
Is there any significant incompatibilities? Will it require changes in app that uses 2.0?