Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Scala in Practice

521 views

Published on

Scala is a programming language that mixes object oriented and functional programming in a powerful and flexible way. While it can not be considered as a mainstream language, it has seen a growing adoption trend.An important ingredient for this diffusion is its complete interoperability with Java and the fact that it runs on a solid platform such as the JVM.
It is currently the 4th most loved programming language and the 2nd top paying technology of 2016 (StackOverflow Developers Survey).
These slides have been used for a 4h seminar at the University of Cagliari the 17th of December 2016

Published in: Software
  • Be the first to comment

Scala in Practice

  1. 1. SCALA IN PRACTICE FRANCESCO USAI
  2. 2. CAPITAL AT RISK
  3. 3. OUTLINE 1. Introduction to Scala 2. The basics 3. Fundamental data structures 4. Web services with Akka-Http a) the language b) a bit of history c) the ecosystem d) SBT & Scala REPL
  4. 4. OUTLINE 1. Introduction to Scala 2. The basics 3. Fundamental data structures 4. Web services with Akka-Http a) values b) functions c) classes and case classes d) objects and traits
  5. 5. OUTLINE 1. Introduction to Scala 2. The basics 3. Fundamental data structures 4. Web services with Akka-Http a) List[T] b) Option[T] c) Map[T], Try[T], Future[T]
  6. 6. OUTLINE 1. Introduction to Scala 2. The basics 3. Fundamental data structures 4. Web services with Akka-Http a) Akka and Akka-Http b) directives & routes c) (un)marshalling d) a simple web service
  7. 7. LET’S START!
  8. 8. SCALA, WHAT Technically, Scala is a blend of object-oriented and functional programming concepts in a statically typed language. Martin Odersky § object-oriented => encourages to structure code into classes and objects § functional => embraces typical functional programming structures: immutability, higher order functions, pattern matching, tuples, etc § statically typed => enforces type safety and program correctness
  9. 9. SCALA, WHY § Programming with Scala is fun! § Similar feel of dynamic languages, but with type safety. § Less boilerplate than usual object-oriented languages. § Succint code, less to write 👍 less to read 👍 👍 👍 § Complete, consistent and pleasant API. § Immutability by default 👉 easier to reason about the code. § Runs on the JVM and interoperates with Java § You can use your favourite Java libraries … in Scala. § There are an increasing number of job offers with higher average salary
  10. 10. SCALA, WHO – WHERE - WHEN § Martin Odersky, professor at ETH and then EPFL (Switzerland) § Designer of Java’s generics and javac compiler § 2004. First public release for both JVM and .NET § 2006. v2.0 released § 2011. Typesafe(now Lightbend) for commercial support, training, etc § 2012. Dropped support for .NET § 2016/11. Scala 2.12 released with new compiler, full Java 8 interop
  11. 11. SCALA, work offers and trend
  12. 12. SCALA, work offers and trend
  13. 13. ECOSYSTEM squeryl Web Frameworks
  14. 14. ECOSYSTEM squeryl Toolkit and runtime For concurrent applications
  15. 15. ECOSYSTEM squeryl ORM
  16. 16. ECOSYSTEM squeryl Fast Data
  17. 17. ECOSYSTEM and more
  18. 18. SBT and REPL § Source Build Tool § Similar to Maven and Ant § Uses Scala and it is compiled § Includes a Scala Console
  19. 19. BACK TO BASICS
  20. 20. BASIC SYNTAX § Declaring a variable with var // we will not talk about that
  21. 21. BASIC SYNTAX § Declaring a variable with var § For loops // we will not talk about that either
  22. 22. BASIC SYNTAX § Declaring a variable with var § For loops § While loops // neither this one
  23. 23. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val val message : String = “Hello Scala” ; Optional, type is inferred. Optional, discouraged
  24. 24. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val val message = “Hello Scala” // type String is inferred val pair = (1,2) // tuples work as in OCaml // access tuple fields with with ._<n> (5, “ciao”)._2 // “ciao”
  25. 25. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val § Declaring a function with def def add(a: Int, b: Int) : Int = { a + b } Optional, return type is inferred Mandatory for recursive functions
  26. 26. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val § Declaring a function with def def add(a: Int, b: Int) : Int = { a + b } Optional, for one liners
  27. 27. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val § Declaring a function with def def add(a: Int, b: Int) = a + b > add: (a: Int, b: Int)Int
  28. 28. BASIC SYNTAX § Declaring a variable with var § For loops § While loops § Declaring a value with val § Declaring a function with def § If-then-else: remember is an expression, not a command if(x == 0) “zero” else “not-zero”
  29. 29. OCaml Scala • Both values and functions declared with let keyword • Super duper type inference • Functions curried by default let add a b = a + b int -> int -> int § Functional with objects • Use val to declare values • Use def to declare functions • Type annotation is often needed • Functions take argument tuples by default def add(a: Int, b: Int) = a + b def add(a: Int)(b: Int) = a + b § Object Oriented and Functional § Complete interoperability with Java
  30. 30. DEMO The unavoidable factorial
  31. 31. EXCERCISE The unavoidable factorial Write a working implementation
  32. 32. FUN WITH FUNCTIONS § Higher order functions def operator(a:Int, b: Int)(f: (Int,Int) => Int) = f(a,b) // (Int, Int) => ((Int, Int) => Int) => Int operator(5, 6)((a,b) => a + b) // 11 operator(5, 6){(a,b) => a + b} // 11. avoid for 1-liners operator(5, 6)(_ + _) // 11. the most concise def add(a: Int, b: Int) = a + b operator(5, 6)(add) // 11. when f is complex/long/reusable
  33. 33. FUN WITH FUNCTIONS § Partial application def operator(a:Int, b: Int)(f: (Int,Int) => Int) = f(a,b) // (Int, Int) => ((Int, Int) => Int) => Int def operatorWith5And6 = operator(5, 6) _ // ((Int, Int) => Int) => Int operatorWith5And6(_ * _) // 30
  34. 34. FUN WITH FUNCTIONS § Polymorphism def transform[A, B](f: A => B)(x: A): B = f(x) // ( A => B) => A => B def numberToString(x: Int) = x.toString() def transformNumberToString = transform(numberToString) _ // Int => String transformNumberToString(5L) // “5”
  35. 35. DEMO Higher order functions Partial application
  36. 36. EXCERCISE
  37. 37. HIGHER ORDER FUN - EXCERCISE § Define and use a function that takes two functions and two Ints x and y § That evaluates to a pair with the result of the two functions § Both applied to x and y § It should respect a similar signature g ((Int, Int) => Int) => // function f1 ((Int, Int) => Int) => // function f2 (Int, Int) => (Int,Int) use :paste on REPL to write multi-line statements or to paste code written elsewhere
  38. 38. WHERE ARE THE OBJECTS? Let’s define some class!
  39. 39. CLASSES § Mostly as in Java … but immutable by default. import java.time.LocalDate import java.time.temporal.ChronoUnit class Person(firstName: String, lastName: String = "Rossi", protected val birthday: LocalDate){ val fullName = s"$firstName $lastName” def age = ChronoUnit.YEARS.between(birthday, LocalDate.now) def isOlderThan(other: Person): Boolean = birthday.isBefore(other.birthday) } new Person( ”Giulio”, “Bianchi”, LocalDate.now) new Person( firstName = “Mario”, birthday = LocalDate.now) constructor parameters default value for lastName
  40. 40. CLASSES § Mostly as in Java … but immutable by default. import java.time.LocalDate import java.time.temporal.ChronoUnit class Person(firstName: String, lastName: String = "Rossi", protected val birthday: LocalDate){ val fullName = s"$firstName $lastName” def age = ChronoUnit.YEARS.between(birthday, LocalDate.now) def isOlderThan(other: Person): Boolean = birthday.isBefore(other.birthday) } constructor parameter bound to public value
  41. 41. CLASSES § Mostly as in Java … but immutable by default. import java.time.LocalDate import java.time.temporal.ChronoUnit class Person(firstName: String, lastName: String = "Rossi", protected val birthday: LocalDate){ val fullName = s"$firstName $lastName” def age = ChronoUnit.YEARS.between(birthday, LocalDate.now) def isOlderThan(other: Person): Boolean = birthday.isBefore(other.birthday) } public value initiliazation
  42. 42. CLASSES § Mostly as in Java … but immutable by default. import java.time.LocalDate import java.time.temporal.ChronoUnit class Person(firstName: String, lastName: String = "Rossi", protected val birthday: LocalDate){ val fullName = s"$firstName $lastName” def age = ChronoUnit.YEARS.between(birthday, LocalDate.now) def isOlderThan(other: Person): Boolean = birthday.isBefore(other.birthday) } method without parameters
  43. 43. CLASSES § Mostly as in Java … but immutable by default. import java.time.LocalDate import java.time.temporal.ChronoUnit class Person(firstName: String, lastName: String = "Rossi", protected val birthday: LocalDate){ val fullName = s"$firstName $lastName” def age = ChronoUnit.YEARS.between(birthday, LocalDate.now) def isOlderThan(other: Person): Boolean = birthday.isBefore(other.birthday) } method with parameters
  44. 44. CLASSES – wait a minute § Everything is an object – there are no primitive types (int, long, double, …) § Operators for Int, Long, Double, … are methods 5 + 6 /* is the same as */ (5).+(6) § Can call 1-arg methods as infix operators (do it juditiously) val giulio = new Person( ”Giulio”, “Bianchi”, LocalDate.of(1990, 12, 20) val mario = new Person( firstName = “Mario”, LocalDate.of(1991, 11, 17) giulio.isOlderThan(mario) /* sames as */ giulio isOlderThan mario
  45. 45. OBJECTS § Hybrid between Singleton/static class § Have no constructor § fields and methods can be imported as packages -> import DateUtils._ import java.time.LocalDate // yeah, importing java libraries object DateUtils{ def today = LocalDate.now val monthsInYear = 12 }
  46. 46. COMPANION OBJECTS § If a class and an object with same name are defined in the same file § The object can access all the class’ fields § A way to define what in Java would be static fields and methods import java.time.LocalDate class Person(val fullName: String, val birthday: LocalDate) object Person{ def older(me: Person, other: Person) = { if(me.birthday.isBefore(other.birthday)){ me } else { other } } }
  47. 47. CASE CLASSES case class Person(firstName: String, lastName: String, birthday: LocalDate) • all constructor parameters are also public values • does not require new to be instantiated • default implementation for equals, getHashCode, toString * • copy method • instances of case classes can be decomposed through pattern matching • Scala way for defining algebraic data types * Highly recommanded for classes in Java -> Effective Java – items 8, 9, 10
  48. 48. CASE CLASSES - example OCAML type number = Zero | Succ of number let numberToInt n = match n with | Zero -> 0 | Succ(x) -> 1 + numberToInt x SCALA sealed abstract class Number object Zero extends Number case class Succ(n:Number) extends Number def numberToInt(n: Number) : Int = n match { case Zero => 0 case Succ(x) => 1 + numberToInt(x) }
  49. 49. TRAITS § Like Java interfaces – more like Java 8 interfaces § Can contain virtual as well as concrete methods § Can contain value definitions § Multiple traits can be mixed in a single class(or object) abstract class Pokemon trait WaterAttacks{ def waterAttack(): Unit} trait PsychicAttacks{ def confusion() = println("Psyduck! Psyduck!") } class Psyduck extends Pokemon with WaterAttacks with PsychicAttacks{ override def waterAttack() = println("Water Pulse!")} (new Psyduck).waterAttack()
  50. 50. EXCERCISE Expression evaluation
  51. 51. PATTERN MATCHING - EXCERCISE § types needed to evaluate basic integer arithmetic expressions (*, / , %, +, -) § define a function for evaluating these expressions. use :paste on REPL to write multi-line statements or to paste code written elsewhere
  52. 52. COLLECTIONS & MORE
  53. 53. LIST [T] § Core of most functional languages. Behave mostly as OCaml § Powerful API to traverse, transform, filter, etc… with high order functions! val emptyList: List[Int] = Nil // or List() or List.empty val numbers = List(2, 4, 5, 12, 20) // here we have type inference val moreNumbers = 6 :: 14 :: numbers // prepend --> [6, 14, 2, 4, 5, 12, 20] numbers.head // 2 numbers.tail // [4, 5, 12, 20] numbers(3) // 12 moreNumbers.filter(x => x > 10) // [14, 12, 20] moreNumbers.filterNot( _ > 10) // [6, 2, 4, 5] moreNumbers.partition(x => x > 10) // ( [14, 12, 20], [6, 2, 4, 5] )
  54. 54. LIST [T] val moreNumbers = 6 :: 14 :: List(2, 4, 5, 12, 20) //prepend as in Ocaml def isEven(x: Int) = x % 2 == 0 moreNumbers.take(3) // [6, 14, 2] moreNumbers.drop(5) // [12, 20] moreNumbers.takeWhile(isEven) // [6, 14, 2, 4] moreNumbers.grouped(3) // [[6, 14, 2], [4, 5, 12], [20]] moreNumbers.grouped(3).flatten // [6, 14, 2, 4, 5, 12, 20] // flatten turns a List[List[T]] into a corresponding List[T]
  55. 55. LIST [T] val otherNumbers = (1 to 5).toList // fancy way to generate sequences List (1, 2, 3).map(f) /* equivalent to */ List ( f(1), f(2), f(3) ) otherNumbers.map(x => (1 to x).toList) // [[1], [1,2], [1,2,3], … ]
  56. 56. LIST [T] val otherNumbers = (1 to 5).toList // fancy way to generate sequences List (1, 2, 3).reduce(f) /* equivalent to */ f( f(1, 2), 3) otherNumbers.reduce((a,b) => a * b) // 120 (((1*2) *3) *4) * 5
  57. 57. LIST [T] val otherNumbers = (1 to 5).toList // fancy way to generate sequences List (1, 2, 3).fold(100)(f) /* equivalent to */ f( f( f(100,1), 2), 3) otherNumbers.fold(3)((a,b) => a * b) // 360 ((((3*1) *2) *3) * 4) * 5
  58. 58. LIST [T] val otherNumbers = (1 to 5).toList // fancy way to generate sequences List (1, 2, 3).scan(100)(f) // is a map to the fold's intermediate results otherNumbers.scan(3)((a,b) => a * b) // List(3, 3, 6, 18, 72, 360)
  59. 59. LIST [T] – map, flatten, flatMap val otherNumbers = (1 to 5).toList // fancy way to generate sequences otherNumbers.map( (1 to _).toList) // [[1], [1,2], [1,2,3], … ] otherNumbers.map( (1 to _).toList).flatten // [1, 1, 2, 1, 2, 3, … ] otherNumbers.flatMap( (1 to _).toList) // [1, 1, 2, 1, 2, 3, … ] In this context map [T, B]( f: T => B) : List[B] flatMap[T, B]( f: T => List[B]) : List[B] In Scala there are a number of containers that expose map and flatMap
  60. 60. LIST [T] – other goodies val cities = List(“Rome”, “Paris”, “Madrid”, “London”) val countries = List(“Italy”, “France”, “Spain”, “Great Britain”) (cities zip countries).toMap // Map[String, String] val numbers = (1 to 25 by 3).toList numbers.groupBy( n => n % 2 == 0) // Map[Boolean, List[Int]] [false -> List(1, 7, 13, 19, 25), true -> List(4, 10, 16, 22)] Other Scala collections similar to List[T]. Especially Vector[T] and Array[T]. Mostly used for Java interoperability
  61. 61. MAP [K, V] § Called Map in Java and C++ § Called Dictionary in C# and Python § Called Associative Arrays in PHP § A container for mappings from a value of type K to a value of type V val citiesToCountry = (cities zip countries).toMap val withPadania = citiesToCountry + ( “Milano” -> “Padania” ) val withoutParis = withPadania – “Paris” § Getting the elements updated.get(“Napoli”) // get(key: K): Option[V] updated(“Riga”) // NoSuchElementException if not found
  62. 62. MAP [K, V] § A container for mappings from a value of type K to a value of type V § If accessed through ( ) it can be seen as a partial function K => V List(“Rome”, ”London”).map(withoutParis) // List(“Italy”, “Great Britain”) § And transformed to a total function K -> V using .withDefaultValue val total = citiesAndCountries.withDefaultValue(“Unknown”) total(“Cagliari”) // unknown total.get(“Cagliari”) // -> None § Operations to transform and iterate. Map, mapValues, flatMap, filter, foreach,
  63. 63. EXCERCISE Operations on Collections
  64. 64. COLLECTIONS - EXCERCISE 1. Get the sum of the files size in your user folder (we can limit at the top level) 2. Build a map from the folder name to its total size § You will need import java.io._ val home = new File(<folder name>) home.listFiles use :paste on REPL to write multi-line statements or to paste code written elsewhere
  65. 65. OPTION [T] def getUserById(id: Long) : User // what if the user is not found?
  66. 66. OPTION [T] § The Option type denotes with Some[T] the presence of a value, and with None its absence § Represents a value that can be or not be there. In Ocaml should be something similar to: type ‘a option = Some of ‘a | None How do you expect it to be implemented in Scala? sealed abstract class Option[T] case object None extends Option[T] case class Some[T](x: T) extends Option[T] § Make Tony happy, never (ever ever) return null § return or expect an Option[T] whenever the value can be missing.
  67. 67. OPTION [T] def getUserById(id: Long) : Option[User] // explicit uncertainty case class User(firstName: String, middleName: Option[String], … ) val maybeUser = db.getUserById(5) val user = maybeUser.getOrElse( throw new UserNotFoundException(…) ) OR val user = db.getUserById(5) match { case Some(found) => found case None => throw new UserNotFoundException(…) } OR (but better not to) db.getUserById(5).get // this will throw NoSuchElementException if None
  68. 68. OPTION [T] def getUserById(id: Long) : Option[User] // explicit uncertainty val maybeUser = db.getUserById(5) val user = maybeUser.getOrElse( throw new UserNotFoundException(…) ) OR val user = db.getUserById(5) match { case Some(found) => found case None => throw new UserNotFoundException(…)} § What if I need to pass the User to another function or compute something? seems cumbersome
  69. 69. map [T, B]( f: T => B): Option[B] Some(5).map(x * 10) // --> Some(50) None.map(x * 10) // --> None § Let’s naively count the number of hashtags used by an user getUser(5).map { user => getTweets( user ).map { tweet => // getUserTweets: List[String] tweet.count( _ == ’#' ) // count elements by a predicate }.reduce(_ + _) // or better use .sum } Some(161247) or None if the user has been not found OPTION [T] – map!
  70. 70. def getUserById(id: Long): Option[User] def getUserTweets(user: User): Option[List[String] val maybeMaybeUserTweets = db.getUserById(5).map( user => getUserTweets(user)) map [T, B]( f: T => B): Option[B] § Here B is Option[List[String]] so § Option[B] is then Option[Option[List[String]]] OPTION [T] – map!
  71. 71. def getUserById(id: Long): Option[User] def getUserTweets(user: User): Option[List[String] flatMap [T, B]( f: T => Option[B]): Option[B] getUser(5).flatMap { user => getTweets(user).map { tweets => tweets.map { tweet => tweet.count( _ == ’#' ) }.sum } } Some(161247) or None OPTION [T]
  72. 72. def getUserById(id: Long): Option[User] def getUserTweets(user: User): Option[List[String] flatMap [T, B]( f: T => Option[B]): Option[B] Here B is List[String] hence the result is an Option[List[String]] getUser(5).flatMap(getTweets).map( _.map( _.count( _ == ’#' )).sum ) Some(161247) or None OPTION [T] – flatMap!
  73. 73. Some(5).contains(6) // -> false Some(13).forall(_ % 2 == 0) // -> false None.contains(“ciao”) // -> false Some(24) .filterNot( x => x < 10) // -> Some(24) .map( x => x / 3) // -> Some(8) .filter( x => x % 5 == 0) // -> None .foreach(println) What’s the output? It does not print anything, since None contains no values OPTION [T] as a one item List[T]
  74. 74. § A number of Scala containers allow to encapsulate certain mechanisms § Most notably Option[T] // a function that can return a value or not Try[T] // a function that can complete successfully or fail Future[T] // a function that can take some time to complete § And others that support map and flatMap operations allowing to concatenate manipulations of these containers in an coherent way MONADIC DESIGN
  75. 75. § Dealing with exceptions in Scala can be done in two ways § (Almost) Java Style try{ 10/0 } catch { case e: ArithmeticException => println(e.getMessage) case e: Exception => println(“never catch generic exception”) } § Using Try[T] Try{ 10/0 } match { case Failure(exc) => println(exc.getMessage) case Success(value) => value } TRY-CATCH or TRY [T] What’s the difference?
  76. 76. § Try is a data structure. You can assign it to values. § The API is mostly the same as List[T] and Option[T] map [T, B]( f: T => B) : Try[B] flatMap [T, B]( f: T => Try[B]): Try[B] filter, foreach, flatten, … def getUserById(id: Long): Try[Option[User]] // db connection may fail val divByTwo = Try{10 / 2} val divByZero = Try{10 / 0} divByZero.map{ number => number * 3 } // Failure(ArithmeticException) divByTwo.map{ number => number * 3 } // Success(15) TRY [T]
  77. 77. § Encapsulates a long computation (e.g. calling a web service) § A way to handle concurrency § A Future is an object that at some point may contain the required value § Mostly same API of List[T], Option[T], Try[T] map [T, B]( f: T => B) : Future[B] flatMap [T, B]( f: T => Future[B]): Future[B] filter, foreach, flatten, … def slowIncrement(x: Int) = Future{ Thread.sleep(1000); x + 1 } § map used to chain a synchronous operation § flatMap to chain an asynchronous one FUTURE [T]
  78. 78. WEB SERVICES WITH AKKA-HTTP
  79. 79. resilient elastic distributed real-time transaction processing
  80. 80. Actor 1 Actor 2 Actor 3 Actor 4 ACTOR § Primitive unit of computation § Has a mail box § Reads one message at a time from its mailbox § Performs a different action depending on the message it receives: § Can send messages to other actors § Can create other actors § Can mutate its state and change the way to react to next messages § No shared memory with other actors
  81. 81. § Concurrency is message based and asynchronous § No synchronization primitives § Actor systems can span multiple jvms § Actors interact in the same way on the same host or separate host § Actor hierarchies provide supervision and fault-reaction § Fault-tolerance, error is just an event that needs to be handled
  82. 82. § Full Http Server and Http Client implementation § The main goal is to write/consume RESTful APIs § Build upon Akka § Not a framework, but a set of libraries § Formerly called Spray.io, later incorporated in Akka § Different API levels for implementing the same things § Takes advantage of Scala ability to create Domain Specific Languages § Routing DSL § Marshalling/UnMarshaling of json/xml requests
  83. 83. ROUTING DSL § An HTTP route is composed of an HTTP Method and a relative URI § Each routing hierarchy is created upon Directives GET /unica/students/45353 pathPrefix(“unica”){ path(“students” / LongNumber){ id => // / is an used to chain path segments get{ complete(studentsService.getStudent(id)) } } }
  84. 84. DIRECTIVES § Each directive is a small (and nestable) building block § Each directive can filter/transform/complete a request or extract values from it § HttpMethods (get, post, put, …) § Extract headers, url parameters or entities (headerValue, entity, parameters) § Routes are concatenated using the ~ (tilde) operator
  85. 85. accept if starts with: /unica/students accept only if URL is /unica/students Accept only if method is POST Unmarshall body to instance of Student Businness logic, save the student Respond with Status 201 If rejected from the above Accept only if URL /unica/students/:id Accept only if method is GET Complete calling the businness logic pathPrefix(“unica” / “students“){ pathEnd{ post{ entity(as[Student]){ request => complete{ studentsService.register(request) Created } } } ~ path(LongNumber){ id => get{ complete(studentsService.getStudent(id)) } } } } Incoming Request
  86. 86. SHOW US SOME CODE!
  87. 87. TO WRAP UP
  88. 88. § http://www.scala-lang.org/ § http://docs.scala-lang.org/tutorials/ § https://twitter.github.io/scala_school/ § https://www.coursera.org/specializations/scala § akka.io § http://doc.akka.io/docs/akka-http/current/index.html § Programming in Scala 3rd Edition – Martin Odersky § Akka in Action - Raymond Roestenburg § francescousai.info/blog WHERE TO GO FROM HERE
  89. 89. TRY SCALA TRY AKKA-HTTP HAVE FUN
  90. 90. www.moneyfarm.com www.moneyfarm.com/it/jobs/ francesco.usai@moneyfarm.com

×