Oleksii Petinov during his presentation gave the audience the overview of his vision of Scala pros and contras. In his vision Scala smoothly integrates features of object-oriented and functional languages, enabling Java and other programmers to be more productive.
There is admittedly some truth to the statement that “Scala is complex”, but the learning curve is well worth the investment.
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
From Java to Scala - advantages and possible risks
1. From Java to Scala - advantages
and possible risks
Oleksii Petinov
Scala engineer,
Newground
2. Scala = “scalable”
● Scalable = extensible
small core – everything else are libraries. Efficient for both small utilities or
large-scale systems. Very good for building libraries (Play, Akka etc)
var capital = Map("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
● Multiparadigm = FP + OOP
3. Who is using Scala
● Twitter
● Foursquare
● UK Guardian
● Sony Pictures Entertainment
● SAP
● Xerox
● Novell
● Netflix
● Tumblr (partially)
5. Concise
Java:
class MyClass {
private int index;
private String name;
public MyClass(int index, String name) {
this.index = index;
this.name = name;
}
}
Scala:
case class MyClass(index: Int, name: String)
6. High-level
Java:
boolean nameHasUpperCase = false;
for (int i = 0; i < name.length(); ++i) {
if (Character.isUpperCase(name.charAt(i))) {
nameHasUpperCase = true;
break;
}
}
Scala:
val nameHasUpperCase = name.exists(_.isUpper)
7. Staticly-typed
● Advanced static type system
● Verifiable properties
● Sophisticated type inference system
val x = new HashMap[Int, String]()
val x: Map[Int, String] = new HashMap()
● Duck typing
def callSpeak[A <: { def speak(): Unit }](obj: A) {
// code here ...
obj.speak()
}
8. FunctionalWith side-effects
class Cafe {
def buyCoffee(cc: CreditCard): Coffee = {
val cup = new Coffee()
cc.charge(cup.price)
cup
}
}
With side-effects and payments object:
class Cafe {
def buyCoffee(cc: CreditCard, p: Payments): Coffee = {
val cup = new Coffee()
p.charge(cc, cup.price)
cup
}
}
Side-effects free – returning Charge object:
class Cafe {
def buyCoffee(cc: CreditCard): (Coffee, Charge) = {
val cup = new Coffee()
(cup, Charge(cc, cup.price))
}
}
9. Small core, extensibility
Library class `scala.BigInt` feels natural – like part of the language:
def factorial(x: BigInt): BigInt =
if (x == 0) 1 else x * factorial(x - 1)
In Java it feels like more like library type:
import java.math.BigInteger
def factorial(x: BigInteger): BigInteger =
if (x == BigInteger.ZERO)
BigInteger.ONE
else
x.multiply(factorial(x.subtract(BigInteger.ONE)))
11. High level – control structures
Definition (open a resource, operate on it, and then close the resource):
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
Usage:
val file = new File("date.txt")
withPrintWriter(file) {
writer => writer.println(new java.util.Date)
}
12. DSLs (internal)
Scalatest:
test("pop is invoked on an empty stack") {
val emptyStack = new Stack[String]
evaluating { emptyStack.pop() } should produce [NoSuchElementException]
emptyStack should be ('empty)
}
Squeryl:
def songCountByArtistId: Query[GroupWithMeasures[Long,Long]] =
from(artists, songs)((a,s) =>
where(a.id === s.artistId)
groupBy(a.id)
compute(count)
)
13. Traits
trait Philosophical {
def philosophize() {
println("I consume memory, therefore I am!")
}
}
class Animal
trait HasLegs
class Frog extends Animal with Philosophical with HasLegs {
override def toString = "green"
}
15. Operator notation for methods
scala> val sum = 1 + 2 // Scala invokes (1).+(2)
sum: Int = 3
scala> val a = List(1,2,3)
a: List[Int] = List(1, 2, 3)
scala> val b = List(4,5,6)
b: List[Int] = List(4, 5, 6)
scala> a ::: b // Scala invokes b.:::(a)
res0: List[Int] = List(1, 2, 3, 4, 5, 6)
16. Local functions
def processFile(filename: String, width: Int) {
def processLine(line: String) {
if (line.length > width)
println(filename +": "+ line)
}
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(line)
}
17. Function literals and values
● Function literals and values
scala> val increase = (x: Int) => x + 1
increase: (Int) => Int = <function1>
scala> increase(10)
res0: Int = 11
● Function literals as predicates
someNumbers.filter(x => x > 0)
19. Handling errors: exceptions
def purchaseCoffee(money: Int): Coffee =
brewCoffee(buyBeans(money))
def buyBeans(money: Int): Beans =
if (money < price)
throw new Exception(s"Not enough money to buy beans for a coffee, need
$price")
else
new Beans
def brewCoffee(beans: Beans): Coffee = {
// simulate a faulty grinder that fails 25% of the time
if (Math.random < 0.25)
throw new Exception("Faulty grinder failed to grind beans!")
else
new Coffee
}
20. Handling exceptions functionally
case class FailureReason(reason: String)
def purchaseCoffee(money: Int): Either[FailureReason, Coffee] =
for {
beans <- buyBeans(money).right
coffee <- brewCoffee(beans).right
} yield coffee
def buyBeans(money: Int): Either[FailureReason, Beans] =
if (money < price)
Left(FailureReason(s"Not enough money to buy beans for a coffee, need $price"))
else
Right(new Beans)
def brewCoffee(beans: Beans): Either[FailureReason, Coffee] = {
if (Math.random < 0.25)
Left(FailureReason("Faulty grinder failed to grind beans!"))
else
Right(new Coffee)
}
21. Partially applied functions
Original function:
def withTax(cost: Float, state: String) = { /* Some complicated lookup
table */ }
Partially applied function:
val locallyTaxed = withTax(_: Float, "NY")
val costOfApples = locallyTaxed(price("apples"))
Converting method to function value:
val func = method //Wrong
val func :Int => Int = method //This works
val func = method _ //Or like this
22. Scala complexity
● Scala is not complex, but it allows you to compactly
express complex ideas.
● Understanding compact expression of complex
ideas can be hard.
val x:Option[Int] = 2.some // scalaz enrichment for options
val y:Option[Int] = 3.some
val z:Option[Int] = 5.some
// With scalaz we can do the following instead of for or maps
// First we need to put the function in the right form, curried.
// To understand why please read the references I've given below.
val addInts = ( (a:Int, b:Int, c:Int) => a + b + c ).curried
val sum = x <*> (y <*> (z map addInts)) // Some(10)
23. Tail recursion
Tail-recursive function:
@tailrec
def approximate(guess: Double): Double =
if (isGoodEnough(guess)) guess
else approximate(improve(guess))
}
Compiles to loop and performance-wise is the same as:
def approximateLoop(initialGuess: Double): Double = {
var guess = initialGuess
while (!isGoodEnough(guess))
guess = improve(guess)
guess
}
28. Enumerations and case objects
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
currency match {
case EUR =>
case UnknownCurrency(code) =>
}
31. Case classes
abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
scala> val v = Var("x")
v: Var = Var(x)
scala> val op = BinOp("+", Number(1), v)
op: BinOp = BinOp(+,Number(1.0),Var(x))
scala> v.name
res0: String = x
scala> println(op)
BinOp(+,Number(1.0),Var(x))
scala> op.right == Var("x")
res3: Boolean = true
scala> op.copy(operator = "-")
res4: BinOp = BinOp(-, Number(1.0),Var(x))
32. Pattern matching
expr match {
case BinOp(op, left, right) =>
println(expr + " is a binary operation")
case somethingElse => "not expression: "+ somethingElse
case _ =>
}
case 0 => "zero"
case BinOp("+", e, Number(0)) =>
println("a deep match")
case List(0, _, _) => println("found it")
case s: String => s.length
case BinOp("+", x, y) if x == y =>
BinOp("*", x, Number(2))
33. Covering all cases
Sealed classes
sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
34. Option instead of null
scala> var x : Option[String] = None
x: Option[String] = None
scala> x.get
java.util.NoSuchElementException: None.get in
scala> x.getOrElse("default")
res0: String = default
scala> x = Some("Now Initialized")
x: Option[String] = Some(Now Initialized)
scala> x.get
res0: java.lang.String = Now Initialized
scala> x.getOrElse("default")
res1: java.lang.String = Now Initialized
val myData = map.get(userId).map(doFunction).map(toHtml)
println(myData.getOrElse(noDataHtml))
35. Lists
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
val oneTwoThree = 1 :: 2 :: 3 :: Nil
list.head // Returns the first element in the thrill list
list.init // Returns a list of all but the last element in the thrill list
list.tail // Returns the thrill list minus its first element
41. Type parametrization - 1
class Queue[T](
private val leading: List[T],
private val trailing: List[T]
) {
private def mirror =
if (leading.isEmpty)
new Queue(trailing.reverse, Nil)
else
this
def head = mirror.leading.head
def tail = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def enqueue(x: T) =
new Queue(leading, x :: trailing)
}
42. Type parametrization - covariance
scala> def doesNotCompile(q: Queue) {}
<console>:5: error: trait Queue takes type parameters
def doesNotCompile(q: Queue) {}
ˆ
scala> def doesCompile(q: Queue[AnyRef]) {}
doesCompile: (Queue[AnyRef])Unit
trait Queue[+T] { ... }
trait Queue[-T] { ... }
// if a generic parameter type appears as the type of a method parameter,
// the containing class or trait may not be covariant in that type parameter
class Queue[+T] {
def enqueue(x: T) = ???
}
43. Contravariance and lower bounds
trait OutputChannel[-T] {
def write(x: T)
}
trait Function1[-S,+T] {
def apply(x: S): T
}
44. Upper boundsclass Animal
class Dog extends Animal
class Puppy extends Dog
object ScalaUpperBoundsTest {
def main(args: Array[String]) {
val animal = new Animal
val dog = new Dog
val puppy = new Puppy
val animalCarer = new AnimalCarer
//animalCarer.display(animal)
animalCarer.display(dog)
animalCarer.display(puppy)
}
}
class AnimalCarer{
def display [T <: Dog](t: T){
println(t)
}
}
45. Implicit conversions
val letters = "ABCEDEFG".foldLeft("")((acc, c) => acc + c + " ")
println(letters) // A B C E D E F G
object Predef extends LowPriorityImplicits with DeprecatedPredef {
...
@inline imlicit def augmentString(x: String): StringOps = new
StringOps(x)
...
}
46. Implicit parameters
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PreferredPrompt,
drink: PreferredDrink) {
println("Welcome, "+ name +". The system is ready.")
print("But while you work, ")
println("why not enjoy a cup of "+ drink.preference +"?")
println(prompt.preference)
}
}
object JoesPrefs {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
}
47. Type classes - ordering
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a
else b
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord._
if (a < b) a else b
}
48. Type classes - JSON
case class FullName(firstName: String, lastName: String, middleName: Option[String] = None)
...
implicit object NameJsonWriter extends JsonFormat[FullName] {
override def write(obj: FullName): JsValue = JsObject(
”firstName” -> JsString(obj.firstName),
”lastName” -> JsString(obj.lastName),
”middleName” -> obj.middleName.map(JsString(_)).getOrElse(JsNull))
override def read(json: JsValue): FullName = {
json.asJsObject.getFields(”firstName”, ”lastName”, ”middleName”) match {
case Seq(JsString(firstName), JsString(lastName), JsString(middleName)) =>
FullName(firstName, lastName, Some(middleName))
case Seq(JsString(firstName), JsString(lastName)) =>
FullName(firstName, lastName)
case _ => throw new DeserializationException("FullName expected")
...
private def anyToAst[T:JsonWriter](any: T): JsValue = any.toJson
private def astToAny[T:JsonReader](ast: JsValue): T = ast.convertTo[T]
}
49. For comprehension
case class Book(title: String, authors: String*)
val books: List[Book] = List(
Book("Structure and Interpretation of Computer Programs", "Abelson, Harold", "Sussman, Gerald J."),
Book("Principles of Compiler Design", "Aho, Alfred", "Ullman, Jeffrey" ),
Book("Programming in Modula-2", "Wirth, Niklaus"),
Book("Elements of ML Programming", "Ullman, Jeffrey"),
Book("The Java Language Specification", "Gosling, James", "Joy, Bill", "Steele, Guy", "Bracha, Gilad")
)
// Then, to find the titles of all books whose author's last name is "Gosling":
for (b <- books; a <- b.authors
if a startsWith "Gosling")
yield b.title
// Or, to find the titles of all books that have the string "Program" in their title:
for (b <- books if (b.title indexOf "Program") >= 0)
yield b.title
// Or, to find the names of all authors that have written at least two books in the database:
for (b1 <- books; b2 <- books if b1 != b2;
a1 <- b1.authors; a2 <- b2.authors if a1 == a2)
yield a1
50. Combinator parser (external DSL)trait ArithmExprParser extends JavaTokenParsers {
sealed abstract class Tree
case class Add(t1: Tree, t2: Tree) extends Tree
case class Sub(t1: Tree, t2: Tree) extends Tree
case class Mul(t1: Tree, t2: Tree) extends Tree
case class Div(t1: Tree, t2: Tree) extends Tree
case class Num(t: Double) extends Tree
def eval(t: Tree): Double = t match {
case Add(t1, t2) => eval(t1)+eval(t2)
case Sub(t1, t2) => eval(t1)-eval(t2)
case Mul(t1, t2) => eval(t1)*eval(t2)
case Div(t1, t2) => eval(t1)/eval(t2)
case Num(t) => t
}
lazy val expr: Parser[Tree] = term ~ rep("[+-]".r ~ term) ^^ {
case t ~ ts => ts.foldLeft(t) {
case (t1, "+" ~ t2) => Add(t1, t2)
case (t1, "-" ~ t2) => Sub(t1, t2)
}
}
lazy val term = factor ~ rep("[*/]".r ~ factor) ^^ {
case t ~ ts => ts.foldLeft(t) {
case (t1, "*" ~ t2) => Mul(t1, t2)
case (t1, "/" ~ t2) => Div(t1, t2)
}
}
lazy val factor = "(" ~> expr <~ ")" | num
lazy val num = floatingPointNumber ^^ { t => Num(t.toDouble) }
}
GET / controllers.Application.index
GET /ws controllers.Application.ws
51. Streamscala> List("a", ”b”, ”c”) zip (Stream from 1)
res5: List[(java.lang.String, Int)] = List((a,1), (b,2), (c,3))
scala> val s = 1 #:: {
| println(”HI”)
| 2
| } #:: {
| println("BAI”)
| 3
| } #:: Stream.empty
s: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> s(0)
res39: Int = 1
scala> s(1)
HI
res40: Int = 2
scala> s(2)
BAI
res41: Int = 3
scala> s
res43: scala.collection.immutable.Stream[Int] = Stream(1, 2, 3)
52. Concurrency – Futures - 1
import ExecutionContext.Implicits.global
val session = SocialNetwork.createSessionFor("user", SocialNetwork.credentials)
val f: Future[List[String]] = Future {
session.getRecentPosts()
}
f onComplete {
case Success(posts) => for (post <- posts) println(post)
case Failure(t) => println("An error has occured: " + t.getMessage)
}
f onSuccess { case posts => for (post <- posts) println(post) }
f onFailure { case t => println("An error has occured: " + t.getMessage) }
@volatile var totalA = 0
val text = Future { "na" * 16 + "BATMAN!!!" }
text onSuccess { case txt => totalA += txt.count(_ == 'a') }
text onSuccess { case txt => totalA += txt.count(_ == 'A') }
53. Concurrency – Futures - 2val rateQuote = Future {
connection.getCurrentValue(USD)
}
{ // Version 1
rateQuote onSuccess { case quote =>
val purchase = Future {
if (isProfitable(quote)) connection.buy(amount, quote)
else throw new Exception("not profitable")
}
purchase onSuccess {
case _ => println("Purchased " + amount + " USD")
}
}
}
{ // Version 2
val purchase = rateQuote map { quote =>
If (isProfitable(quote)) connection.buy(amount, quote)
else throw new Exception("not profitable")
}
purchase onSuccess {
case _ => println("Purchased " + amount + " USD")
}
}
54. Concurrency – Futures - 3
{
val usdQuote = Future { connection.getCurrentValue(USD) }
val chfQuote = Future { connection.getCurrentValue(CHF) }
val purchase = for {
usd <- usdQuote
chf <- chfQuote if isProfitable(usd, chf)
} yield connection.buy(amount, chf)
purchase onSuccess { case _ => println("Purchased " + amount + " CHF") }
}
{
val purchase: Future[Int] = rateQuote map {
quote => connection.buy(amount, quote)
} recover {
case QuoteChangedException() => 0
}
}
55. Concurrency - Promise
import scala.concurrent.ExecutionContext.Implicits.global
val p = Promise[T]()
val f = p.future
val producer = Future {
val r = produceSomething()
if (isInvalid(r))
p failure (new IllegalStateException)
else {
val q = doSomeMoreComputation(r)
p success r
}
continueDoingSomethingUnrelated()
}
val consumer = Future {
startDoingSomething()
f onSuccess { case r => doSomethingWithResult() }
}
56. Reactive - 1
● Responsive - it must react to its users
● Resilient - it must react to failure and stay
available
● Elastic - it must react to variable load
conditions
● Message-driven - it must react to inputs
58. Reactive - 3
case class ParallelRetrievalExampleScala (val cacheRetriever: CacheRetriever, val dbRetriever: DBRetriever) {
def retrieveCustomer(id: Long) : Future[Customer] = {
val cacheFuture = Future { cacheRetriever.getCustomer(id) }
val dbFuture = Future { dbRetriever.getCustomer(id) }
Future.firstCompletedOf(List(cacheFuture, dbFuture))
}
}
public class ParallelRetrievalExample {
final CacheRetriever cacheRetriever;
final DBRetriever dbRetriever;
ParallelRetrievalExample(CacheRetriever cacheRetriever,
DBRetriever dbRetriever) {
this.cacheRetriever = cacheRetriever;
this.dbRetriever = dbRetriever;
}
public Object retrieveCustomer(final long id) {
final CompletableFuture<Object> cacheFuture = CompletableFuture.supplyAsync(() -> {return cacheRetriever.getCustomer(id); });
final CompletableFuture<Object> dbFuture = CompletableFuture.supplyAsync(() -> { return dbRetriever.getCustomer(id); });
return CompletableFuture.anyOf(cacheFuture, dbFuture);
}
}
59. Reactive - 4● def getProductInventoryByPostalCode( productSku: Long, postalCode: String):
Future[(Long, Map[Long, Long])] = {
implicit val ec = ExecutionContext.fromExecutor(new ForkJoinPool())
implicit val timeout = 250 milliseconds
val localInventoryFuture = Future {
inventoryService.currentInventoryInWarehouse(
productSku, postalCode)
}
val overallInventoryFutureByWarehouse = Future {
inventoryService.currentInventoryOverallByWarehouse(
productSku)
}
for {
local <- localInventoryFuture
overall <- overallInventoryFutureByWarehouse
} yield (local, overall)
}
60. Reactive - 5
case object Start
case class CounterMessage(counterValue: Int)
case class CounterTooLargeException(message: String) extends Exception(message)
class SupervisorActor extends Actor with ActorLogging {
override val supervisorStrategy = OneForOneStrategy() { case _: CounterTooLargeException => Restart }
val actor2 = context.actorOf(Props[SecondActor], "second-actor")
val actor1 = context.actorOf(Props(new FirstActor(actor2)), "first-actor")
def receive = { case Start => actor1 ! Start }
}
class AbstractCounterActor extends Actor with ActorLogging {
var counterValue = 0
def receive = { case _ => }
def counterReceive: Receive = LoggingReceive {
case CounterMessage(i) if i < 1000 => counterValue = i
log.info(s"Counter value: $counterValue")
sender ! CounterMessage(counterValue + 1)
case CounterMessage(i) => throw new CounterTooLargeException("Exceeded max value of counter!")
}
override def postRestart(reason: Throwable) = { context.parent ! Start }
}
61. Reactive - 6
class FirstActor(secondActor: ActorRef) extends
AbstractCounterActor {
override def receive = LoggingReceive {
case Start =>
context.become(counterReceive)
log.info("Starting counter passing.")
secondActor ! CounterMessage(counterValue + 1)
}
}
class SecondActor() extends AbstractCounterActor {
override def receive = counterReceive
}
object Example extends App {
val system = ActorSystem("counter-supervision-example")
val supervisor = system.actorOf(Props[SupervisorActor])
supervisor ! Start
}
62. Reactive app – Spray + Slick + Akkaobject ExecutionContexts {
private val contextsSettings = ExecutionContextsSettings()
implicit val dbExecutionContext =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(contextsSettings.blockingThreadCount))
implicit val cpuIntensiveExecutionContext =
ExecutionContext.fromExecutor(Executors.newWorkStealingPool(contextsSettings.cpuIntensiveThreadCount))
}
def accountSignUpRoute = {
respondWithMediaType(MediaTypes.`application/json`) {
(path(ApiRoot / "account") & post) {
entity(as[SignUp]) { signUp =>
onSuccess(accountService.createAccount(signUp)) {
case Left(failure) => complete(StatusCodes.Conflict,
"The email address you provided is already registered to another account")
case Right(acc) =>
respondWithHeader(Location(s"/$ApiRoot/account/${acc.id.get}")) {
setSession(SessionCookie(data = Map("id" -> acc.id.get.toString), path = Some("/"))) {
complete(StatusCodes.Created, acc)
}
}
}
}
}
}
}
63. Reactive app - 2
def createAccount(account: Account): Future[Either[AccountCreationFailure, Account]] =
Future {
db withTransaction { implicit session =>
opTimer.time {
// check that email address does not already exist
accountRepo.retrieveAccountByEmail(account.email) match {
case Some(acc) => Left(AccountCreationFailure(account.email)) // reject account exists
case _ =>
val createdAcc = accountRepo.createAccount(account)
pictureRepo.createPicture(Picture(url = "http://bit.ly/1y3A9HS", accountId =
createdAcc.id.get))
Right(createdAcc)
}
}
}
}
69. Persistence layer - 2private[account]
class Accounts extends MetricsInstrumented with Mappers {
implicit val dbExecContext = ExecutionContexts.dbExecutionContext
import storage.operationSuccessMapper
import scala.slick.jdbc.JdbcBackend.Session
private[this] val logger = Logger[this.type]
private val siteSettings = SiteSettings()
val accounts = TableQuery[AccountTable]
private val qRetrieveAccountByEmail = Compiled( (email: Column[Email]) =>
for { account <- accounts if account.email === email } yield account)
private val qRetrieveAccountPassword = Compiled( (id: Column[Long]) =>
for { account <- accounts if account.id === id } yield account.password )
private val qRetrieveAccount = Compiled( (id: Column[Long]) =>
for { account <- accounts if account.id === id } yield account ) // or accounts.filter(_.id === account.id.get)
def createAccount(account: Account)(implicit session: Session): Account = {
logger.info("account created")
val pw = Password.encrypt(siteSettings.encryptionLogRounds)(account.password)
// set random values
val createdAt = account.createdAt.getOrElse(LocalDateTime.now)
val currentLoginAt, lastLoginAt = LocalDateTime.now
val acc: Account = account.copy(password = pw, createdAt = Some(createdAt))
val id = accounts.returning(accounts.map(_.id)) += acc
account.copy(id = Some(id))
}
def retrieveAccount(id: Long)(implicit session: Session): Option[Account] = {
logger.info("account retrieved1")
qRetrieveAccount(id).firstOption
}
}
70. Akka persistence
● PersistentActor - a persistent, stateful actor. It is able to persist events to a journal and
can react to them in a thread-safe manner. It can be used to implement both command
as well as event sourced actors
● PersistentView - a view is a persistent, stateful actor that receives journaled messages
that have been written by another persistent actor. A view itself does not journal new
messages, instead, it updates internal state only from a persistent actor's replicated
message stream.
● AtLeastOnceDelivery - sends messages with at-least-once delivery semantics to
destinations, also in case of sender and receiver JVM crashes.
● AsyncWriteJournal - a journal stores the sequence of messages sent to a persistent
actor. An application can control which messages are journaled and which are received
by the persistent actor without being journaled. The storage backend of a journal is
pluggable.
● Snapshot store - A snapshot store persists snapshots of a persistent actor's or a view's
internal state. Snapshots are used for optimizing recovery times. The storage backend
of a snapshot store is pluggable.
71. Akka persistence (PersistentActor)
class ExamplePersistentActor extends PersistentActor {
override def persistenceId = "sample-id-1"
var state = ExampleState()
def updateState(event: Evt): Unit =
state = state.updated(event)
def numEvents =
state.size
val receiveRecover: Receive = {
case evt: Evt => updateState(evt)
case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot
}
val receiveCommand: Receive = {
case Cmd(data) =>
persist(Evt(s"${data}-${numEvents}"))(updateState)
persist(Evt(s"${data}-${numEvents + 1}")) { event =>
updateState(event)
context.system.eventStream.publish(event)
}
case "snap" => saveSnapshot(state)
case "print" => println(state)
}
}
72. Persistent actor failure example
class ExamplePersistentActor extends PersistentActor {
override def persistenceId = "sample-id-2"
var received: List[String] = Nil // state
def receiveCommand: Receive = {
case "print" => println(s"received ${received.reverse}")
case "boom" => throw new Exception("boom")
case payload: String =>
persist(payload) { p => received = p :: received }
}
def receiveRecover: Receive = {
case s: String => received = s :: received
}
}
73. Persistent view
●
class ExampleView extends PersistentView {
private var numReplicated = 0
override def persistenceId: String = "sample-id-4"
override def viewId = "sample-view-id-4"
def receive = {
case "snap" =>
println(s"view saving snapshot")
saveSnapshot(numReplicated)
case SnapshotOffer(metadata, snapshot: Int) =>
numReplicated = snapshot
println(s"view received snapshot offer ${snapshot} (metadata = ${metadata})")
case payload if isPersistent =>
numReplicated += 1
println(s"view replayed event ${payload} (num replicated = ${numReplicated})")
case SaveSnapshotSuccess(metadata) =>
println(s"view saved snapshot (metadata = ${metadata})")
case SaveSnapshotFailure(metadata, reason) =>
println(s"view snapshot failure (metadata = ${metadata}), caused by ${reason}")
case payload =>
println(s"view received other message ${payload}")
}
}
74. Lazy vals
scala> object Demo {
val x = { println("initializing x"); "done" }
}
scala> Demo
initializing x
res3: Demo.type = Demo$@17469af
scala> Demo.x
res4: java.lang.String = done
scala> object Demo {
lazy val x = { println("initializing x"); "done" }
}
scala> Demo
res5: Demo.type = Demo$@11dda2d
scala> Demo.x
initializing x
res6: java.lang.String = done
75. ScalaTest – testing specs styles - 1
import org.scalatest.FunSuite
class SetSuite extends FunSuite {
test("An empty Set should have size 0") {
assert(Set.empty.size == 0)
}
test("Invoking head on an empty Set should produce NoSuchElementException") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
import org.scalatest.FlatSpec
class SetSpec extends FlatSpec {
"An empty Set" should "have size 0" in {
assert(Set.empty.size == 0)
}
it should "produce NoSuchElementException when head is invoked" in {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
76. ScalaTest – testing specs styles - 2import org.scalatest.FunSpec
class SetSpec extends FunSpec {
describe("A Set") {
describe("when empty") {
it("should have size 0") {
assert(Set.empty.size == 0)
}
it("should produce NoSuchElementException when head is invoked") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
import org.scalatest.WordSpec
class SetSpec extends WordSpec {
"A Set" when {
"empty" should {
"have size 0" in {
assert(Set.empty.size == 0)
}
"produce NoSuchElementException when head is invoked" in {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
78. Testing with mock objects -
ScalaMock
class ExampleSpec extends FlatSpec with MockFactory {
...
// Function mocks
val m = mockFunction[Int, String]
m expects ( 42) returning "Forty two" once
// Proxy mocks
val m = mock[Turtle]
m expects 'setPosition withArgs(10.0, 10.0)
m expects 'forward withArgs (5.0)
m expects 'getPosition returning(15.0, 10.0)
m expects 'forward withArgs(*) once
m expects 'forward
m expects 'forward anyNumberOfTimes
m stubs 'forward
...
}
79. Property-based testingclass Fraction(n: Int, d: Int) {
require(d != 0)
require(d != Integer.MIN_VALUE)
require(n != Integer.MIN_VALUE)
val numer = if (d < 0) -1 * n else n
val denom = d.abs
override def toString = numer + " / " + denom
}
forAll { (n: Int, d: Int) =>
whenever (d != 0 && d != Integer.MIN_VALUE
&& n != Integer.MIN_VALUE) {
val f = new Fraction(n, d)
if (n < 0 && d < 0 || n > 0 && d > 0)
f.numer should be > 0
else if (n != 0)
f.numer should be < 0
else
f.numer should be === 0
f.denom should be > 0
}
}