0 notes &
Context API
Hi, friends and colleagues!
As I’ve mentioned before in Circumflex 2.0 we are planning to introduce Context API.
So, what is context? Essentially it is a ThreadLocal storage which allows you to share objects within a logical scope. Such logical scope could be anything: database transaction, HTTP request, user session within GUI form, etc.
Context is a rather common concept in programming. It lets you avoid passing too much parameters between collaborating classes (remember plain-old Servlet API where you had to pass explicitly HttpServletRequest and HttpServletResponse between controllers).
Let’s take a lot at some sample code:
class MyServlet extends HttpServlet {
override def doGet(req: HttpServletRequest, res: HttpServletResponse) = {
new MyController1(req, res)
val c = new MyController2
c.doWork(req, res)
c.doMoreWork(req, res)
}
}
class MyController1(req: HttpServletRequest, res: HttpServletResponse) { ... }
class MyController2 {
def doWork(req: HttpServletRequest, res: HttpServletResponse) = { ... }
def doMoreWork(req: HttpServletRequest, res: HttpServletResponse) = { ... }
}
This snippet shows a servlet which passes request and response objects into two controllers. You see, controllers declare that they depend on HttpServletRequest and HttpServletResponse using their constructors and method parameters. If you have thousands of controllers which are executed inside web application and must be aware of request and response, these boilerplates become really annoying. The same could be done using contexts:
class MyServlet extends HttpServlet {
override def doGet(req: HttpServletRequest, res: HttpServletResponse) = {
ctx("request") = req
ctx("response") = res
new MyController1()
val c = new MyController2
c.doWork()
c.doMoreWork()
Context.destroy
}
}
class MyController1 { ... }
class MyController2 {
def doWork = { ... }
def doMoreWork = { ... }
}
You see, the code of servlet becomes a little more complex: context initialization and context destruction are required to define a logical scope, but controllers become much simpler. Since it is clear that controllers are only executed inside context scope, they can use context variables request and response inside their code.
There is, however, one important downside of this approach: classes that rely on context (i.e. expect a context to contain necessary parameters) are harder to debug since they do not “show” their dependencies to the world via public constructors or method parameters. The documentation of such classes should always reflect their expectations about the contents of context.
Circumflex Context API is as simple as one-two-three sequence.
The boundaries of context scope are set using
Context.initandContext.destroy. The latter step is extremely mandatory. If threads are reused using some kind of thread pool, finalization ensures that context does not contain orphaned data from previous owners. Circumflex Web Framework, for example, performs context initialization and finalization usingCircumflexFilter, it also injectsHttpRequestandHttpResponseinto context. You can also add listeners for context initialization and finalization events:Context.addInitListener(c => println("Context initialized.")) Context.addDestroyListener(c => println("Context destroyed."))Circumflex ORM 2.0, for example, uses this approach to ensure that all data access within context scope is performed within a single database transaction without having to redefine the scope of the context.
The last thing to know about context lifecycle is that it is initialized implicitly on first access if it hasn’t been initialized before.
You can work with current context from any code that is ran inside it’s scope. You simply call
Context.get(or a shortcut, thectxmethod of package objectru.circumflex.core). Context extends mutable Scala map, so it should be already familiar to you:// set context variable ctx("key") = value // access context variable ctx.get("key") match { case Some(v: MyExpectedType) => ... case _ => ... }You can also rewrite the above snippet using nice and handy DSL:
// set context variable 'key := value // access context variable 'key.get match { case Some(v: MyExpectedType) => ... case _ => ... }You can configure Circumflex to work with your own context implementation:
package com.myapp class MyContext extends Context { def myUsefulContextScopedMethod = {...} } object MyContext { def get: MyContext = Context.get.asInstanceOf[MyContext] }Then simply override
cx.contextconfiguration parameter:cx.context=com.myapp.MyContextAnd access your own context from your code:
MyContext.get.myUsefulContextScopedMethod
To summarize, there are a lot of things you can do with context. For example, we pass context object as a root data object to Freemarker templates so that you can pass your variables from request routers to views.
Now enough reading. Go and create some software masterpiece with Circumflex. Good luck :)