1

I'm migrating some Scala code from Squeryl to Slick. Everything was going well until I bumped into the topic of transactions. Squeryl made dealing with transactions quite simple: you just need to wrap your code, be it DB-related or not, in a transaction block and you're done.

From the examples I could gather online, it seems one is expected to re-structure the whole project to make it play nicely with its monadic approach and with for-comprehensions, which is something that I would really like to avoid.

Considering an arbitrary piece of code such as:

def f(): Unit = {
    UserRepository.getUser()
    ... some imperative code
    ServiceRepository.getServer()
    ...
    ServerRepository.updateServer(...)
    ...
    UserRepository.insertNewUser(...)
}

is there a way to easily wrap it in some sort of transactional block without having to change the internal logic of the method?

halfer
  • 19,824
  • 17
  • 99
  • 186
devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • I believe if you use `.transactionally` you can't squeeze other code between the calls. Not sure if there are other options to force a db transaction – Ossip Mar 02 '20 at 16:36

1 Answers1

0

is there a way to easily wrap it in some sort of transactional block without having to change the internal logic of the method?

The short answer is no, but it depends on what those calls to UserRepository and other methods return:

  • If they are Future[T] (or if you've blocked to get a value from the future), you've already run the query, so it's a no as you're outside the Slick layer.

  • If they are actions (DBIO) or even queries, then it may not be a change to the internal logic: you would have to compose the actions together, but that's possible with mixing in arbitrary code. For example, flatMap (or a for comprehension) will allow that.

As an example of mixing logic and composing actions you can write code along the lines of:

val action = for {
  user <- UserRepository.getUser // a DBIO[User]
  result <- if user.notPermitted() {
      DBIO.failed(new IOException("Not allowed")
  } else {
      DBIO.successful(user)
  }  
} yield result

Introducing methods and values that work in terms of DBIO (possibly using successful and failed) may make this conversion less of a pain for you. There's an answer that may give more ideas for mixing logic and actions: https://stackoverflow.com/a/31490020/154248

You'd have to execute the final combined action in .transcationally to ensure they all the actions run in a transaction. That's described in: https://scala-slick.org/doc/3.3.2/dbio.html#transactions

There are two other possibilities that come to mind:

  • You could conceivably implement your own back end to Slick. But that seems like a lot of work, and you'd be fighting the ideas in the rest of Slick all the way.

  • Although I'd not normally suggest it, an older version of Slick (Slick 2) seems closer to SQueryl: https://scala-slick.org/doc/2.1.0/connection.html#session-handling ... and that might give you a stepping stone to Slick and then on to Slick 3 as a separate line of work.

Richard Dallaway
  • 4,250
  • 1
  • 28
  • 39