Chain Spray directives to start a transaction and re-use the thread in a route

285 views
Skip to first unread message

Eugene Dzhurinsky

unread,
Jul 27, 2016, 7:57:40 AM7/27/16
to spray.io User List
Hello!

I know that spray directives are decoupled from the route processing code in a way that a directive may use some Future in order to modify the RequestContext.

I have the legacy code that uses transactions delimiters stored in the ThreadLocal variable, and currently I have the some custom code that wraps the Route function into a transaction by initializing that thread local variable. It works fine, however the code looks somehow ugly. Especially when I need to combine that transaction with user authorization, that must look up the user permissions in the database in the scope of the same transaction as the user.

My question is - may I write sort of a directive that will
- supply a user object to the nested route, optionally combined with some other parameters provided by other route directives (Segment, entity etc)
- ensure that the route code will be processed by the same thread as the directive is

It looks like that I need to start a future that will also include the body of the route processing.

The strained example would be something like

(path("user" / Segment) & transaction & authorized("permission1", "permission2", "permission3") {
   
case (userId, effectivePermissions)
     
...
     complete
("Granted")
}

where userId is the String matched form the HTTP path, and effectivePermissions is the set of permissions retrieved from the database.

So the transaction will start a transaction and set the ThreadLocal container, and authorized will re-use that transaction. And the body of the route will be executed in a Future.

Thanks!

Eugene Dzhurinsky

unread,
Jul 29, 2016, 4:26:38 PM7/29/16
to spray.io User List
I kinda solved this task, however I have one small problem:

My directives are defined as

  class TransactionDirective()(implicit txnS: TransactionSupport) extends Directive0 {
    override def happly(f: (HNil) => Route): Route = {
      ctx ⇒
        txnS.wrap {
          f(HNil)(ctx)
        }
    }
  }

  def transactional(implicit txnS: TransactionSupport): Directive0 = new TransactionDirective()

  def userToken(implicit pCtx: PersistenceContext): Directive1[UserContext.Token] = ....



and those compile just fine if I use them as

(get & path("test") & transactional & userToken) {
  case token ⇒ 
     ....
}

However if those directives are used alone, like

(transactional & userToken) {
  case token ⇒
}

I get all sorts of compilation errors due to missing implicit parameters txnS and pCtx

What am I missing there? Do I need to create a magnet to catch those implicit parameters (not sure how to do that thus)

Please advice.

Thanks!

Age Mooij

unread,
Aug 3, 2016, 4:56:18 PM8/3/16
to spray...@googlegroups.com
Hi Eugene

Please see my reply of earlier today on the "Typeclass in a directive" thread. You are running into the same thing, i.e. the mix of directives and implicit parameters are very tricky due to the compiler mixing up the implicit parameter list with the application of the resulting directive.

Sometimes there are workarounds that pass the compiler just enough information to see the difference, like in your first example, but it's not a combination that works well in practice.

Your best options are either: 
a) rewriting your application to use less implicits or to use them differently
b) use the magnet pattern

If you are not writing a library, I'd recommend against using option b).

Hope this helps
Age


--
You received this message because you are subscribed to the Google Groups "spray.io User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
Visit this group at https://groups.google.com/group/spray-user.
To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/551d420c-62d4-4ae5-97f2-6bf802dd4ef7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Eugene Dzhurinsky

unread,
Aug 3, 2016, 11:40:41 PM8/3/16
to spray.io User List
Age, thanks for the reply!

I am not writing a library of any sort, so magnet is definitely an overkill. What I did so far - I created the trait that defines the implicit defs for the types I need to pass into some of my functions (implicit contexts with database-related stuff), so mixing up the trait into the route with overriding the defs with vals provided by the configuration code does the trick.

So perhaps this is the way to go, thanks for the explanation of the problem! I suspected that the compiler simply can not choose the proper curried parameter type and tries to set the inner route as the implicit database context.

Age Mooij

unread,
Aug 4, 2016, 3:26:04 AM8/4/16
to spray...@googlegroups.com

> So perhaps this is the way to go, thanks for the explanation of the problem! I suspected that the compiler simply can not choose the proper curried parameter type and tries to set the inner route as the implicit database context.

Exactly. Glad you were able to work around it.

Age

Reply all
Reply to author
Forward
0 new messages