Skip to content

Commit a6bbe13

Browse files
author
David Pratt
committed
Update README.
1 parent dd478b9 commit a6bbe13

File tree

1 file changed

+60
-8
lines changed

1 file changed

+60
-8
lines changed

README.markdown

+60-8
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,80 @@ Janus
33

44

55
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.
711

812
A quick example -
913

1014
```scala
1115

12-
def externalCall(id: String) : Future[String]
16+
import akka.dispatch.Future
17+
import janus.{Session, Database}
18+
import javax.sql.DataSource
1319

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+
}
1943
}
2044
}
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+
2172
```
2273

2374
The code above creates a connection to a database, and then opens a Session (the logical equivalent to a connection)
2475
against this database. We then open a new transactional boundary and then invoke an external, asynchronous Future-based
2576
web service. Since the return value of the transaction block is Future based, we do not immediately commit the transaction.
2677
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.
2880

2981
Note - if the return value of the transaction block isn't Future based, the transaction is immediately comitted when
3082
the block is evaluated. Of course, the transaction is rolled back if the block either throws an exception or rollback()

0 commit comments

Comments
 (0)