|
3 | 3 |
|
4 | 4 |
|
5 | 5 | Janus is a library focused on simple data access with the ability to interleave database transactions
|
6 |
| -with asynchronous Future-based invocations. |
| 6 | +with asynchronous Future-based invocations. Classic transactional models on the JVM typically rely on a single |
| 7 | +thread-bound transaction context or connection, but this can prove difficult when dealing with asychronous |
| 8 | +external resources. To work around this you either need to implement your external (or otherwise Future based) calls |
| 9 | +as blocking, which tends to be not as robust and elegant. Janus does not utilize the concept of a thread bound |
| 10 | +transaction, but rather the convention is to source a Session implicitly. |
7 | 11 |
|
8 | 12 | A quick example -
|
9 | 13 |
|
10 | 14 | ```scala
|
11 | 15 |
|
12 |
| -def externalCall(id: String) : Future[String] |
| 16 | +import akka.dispatch.Future |
| 17 | +import janus.{Session, Database} |
| 18 | +import javax.sql.DataSource |
13 | 19 |
|
14 |
| -val db = Database(dataSource) |
15 |
| -db.withSession { session => |
16 |
| - session.withTransaction { transaction => |
17 |
| - session.executeSql("insert into test (id, name) VALUES (1, 'Test Value')") |
18 |
| - externalCall("Test Value") |
| 20 | +trait ExternalRepositorySupport { |
| 21 | + def addUser(id: Long, password: String): Future[String] |
| 22 | + def deleteUser(id: Long): Future[Boolean] |
| 23 | +} |
| 24 | + |
| 25 | +trait DataSourceSupport { |
| 26 | + def dataSource: DataSource |
| 27 | +} |
| 28 | + |
| 29 | +case class User(id: Long, name: String) |
| 30 | + |
| 31 | +object User { |
| 32 | + def insertUser(user: User)(implicit session: Session): User = { |
| 33 | + session.withTransaction { transaction => |
| 34 | + session.withPreparedStatement("insert into test (id, name) VALUES (?, ?)") { ps => |
| 35 | + ps.setParam[Long](1, user.id) |
| 36 | + ps.setParam[String](2, user.name) |
| 37 | + if(ps.executeUpdate() != 1) { |
| 38 | + throw new RuntimeException("Could not insert user!") |
| 39 | + } |
| 40 | + } |
| 41 | + user |
| 42 | + } |
19 | 43 | }
|
20 | 44 | }
|
| 45 | + |
| 46 | +trait UserRepository { |
| 47 | + this: ExternalRepositorySupport with DataSourceSupport => |
| 48 | + val db = Database(dataSource) |
| 49 | + |
| 50 | + def createUser(id: Long, name: String, password: String): Future[User] = { |
| 51 | + db.withSession { implicit session => |
| 52 | + session.withTransaction { transaction => |
| 53 | + val user = User.insertUser(User(id, name)) |
| 54 | + addUser(id, password) recoverWith { |
| 55 | + //handle the manual rollback of the external call. |
| 56 | + case e: Throwable => { |
| 57 | + //External call failed - clean it up |
| 58 | + deleteUser(id) |
| 59 | + //re-throw the error |
| 60 | + throw e |
| 61 | + } |
| 62 | + } map { passwordResult => |
| 63 | + //Dont' care about the String returned from the external service - just return the User |
| 64 | + user |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | + |
21 | 72 | ```
|
22 | 73 |
|
23 | 74 | The code above creates a connection to a database, and then opens a Session (the logical equivalent to a connection)
|
24 | 75 | against this database. We then open a new transactional boundary and then invoke an external, asynchronous Future-based
|
25 | 76 | web service. Since the return value of the transaction block is Future based, we do not immediately commit the transaction.
|
26 | 77 | Instead, Janus notices this case, and will then either roll back or commit the transaction only after the Future
|
27 |
| -itself completes. |
| 78 | +itself completes. We do have to handle non-database rollbacks by ourselves, typically using the 'recover' or 'recoverWith' |
| 79 | +methods on the ultimate returned Future. |
28 | 80 |
|
29 | 81 | Note - if the return value of the transaction block isn't Future based, the transaction is immediately comitted when
|
30 | 82 | the block is evaluated. Of course, the transaction is rolled back if the block either throws an exception or rollback()
|
|
0 commit comments