Monad, MonadError (Cats), IO Monad 2. Referencial transparency in FP 3. Pure vs. Impure function 4. Encoding of Type Classes in Scala 5. Lazy vs. Eager evaluation
- inspire you to write functional code in Scala - be familiar with some of the Typelevel libraries as your building blocks Not a goal: - learn how to write micro services - complete all exercises
vehicle: Vehicle, price: Int, completed: Boolean, distance: Option[Int], endDate: Option[LocalDate] ) final case class Trips(trips: Seq[Trip]) object Vehicle extends Enumeration { type Vehicle = Value val Bike, Taxi, Car = Value } final case class CommandResult(count: Int)
http4s is built with cats, cats-effect, fs2-io "org.http4s" %% "http4s-blaze-server" % Version "org.http4s" %% "http4s-blaze-client" % Version % Test “org.http4s" %% "http4s-circe" % Version "org.http4s" %% "http4s-dsl" % Version blaze project is NIO micro framework
GET -> Root / IntVar(id) => // http4s-dsl provides a shortcut to create an F[Response] for Status Code Ok(…) case req @ POST -> Root / “some_string” / someVal => // do something with request object req.as[MyCaseClass].flatMap( _ => Ok(…)) case PUT -> Root / “some_string” / someVal => Ok(…) } … sequence of functions Docs: https://http4s.org/v0.20/dsl/
override def run(args: List[String]): IO[ExitCode] = ??? } Docs: https://typelevel.org/cats-effect/datatypes/ioapp.html - IOApp - is an entry point to a pure FP program - brings ContextShift[IO] and Timer[IO] - provides interruption handler
?], Request[F], Response[F]] Kleisli: A => F[B] def of[F[_]](pf: PartialFunction[Request[F], F[Response[F]]])( implicit F: Sync[F]): HttpRoutes[F] - HttpRoutes.of definition: - Eventually, we will use cats.effect.IO as effect type for F
Returns exactly one result - Can end either in success or failure - Can be canceled* Docs: https://typelevel.org/cats- effect/datatypes/io.html scala> import cats.effect.IO import cats.effect.IO scala> val ioa = IO { println("hey!") } ioa: cats.effect.IO[Unit] = IO$307547358 scala> val program: IO[Unit] = for { _ <- ioa _ <- ioa } yield () program: cats.effect.IO[Unit] = IO$243763162 scala> program.unsafeRunSync() hey! hey!
{ def suspend[A](thunk: => F[A]): F[A] def delay[A](thunk: => A): F[A] = suspend(pure(thunk)) } A Monad that can suspend the execution of side effects in the F[_] context.
library like logback - Usage: class MyClass extends LazyLogging { logger.debug("This is very convenient ;-)”) … } “com.typesafe.scala-logging” %% scala-logging” if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!") - check-enabled-idiom is applied automatically by Scala macros
a route for GET /ping to Ok(“pong”) and leave it assigned to val routes: HttpRoutes[F]. See examples in the code. • instantiate QueryRoutes in org.scalamsi.Module and assign it to val routes: HttpRoutes[F] using such code to combine Uri prefix with a route: Router(apiPrefix -> (qr.routes))
start HTTP server via SBT 1.4. use scala-logging in your Main or Module classes This can be done by extending or mixing-in StrictLogging or LazyLogging traits into your class Once mixed in, a logger instance is in your scope, so just use it to write some useful info message First, extend Main object from IOApp. Override its run method. Implement it by calling stream function with IO as effect type and then call .compile.drain.as(ExitCode.Success) Check Task1 via test: sbt "testOnly *Task1Test” - run shell command: sbt run. Console output should report that web-server is started - (optional check) use curl to call the ping route to get pong reply back: curl localhost:8080/api/v1/trips/ping - stop sbt via Ctrl+C
- supports JVM and Scala.js - allows to parse JSON text into JSON AST, Traverse JSON - Codecs can be derived automatically, semi-automatically or be written manually
• in org.scalamsi.json.CirceJsonCodecs, add implicit Encoder & Decoder instances - Trip, Trips and CommandResult In order to add these instances just leverage circe functions import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} • mixin org.scalamsi.json.CirceJsonCodecs into QueryRoutes & CommandRoutes Docs: https://circe.github.io/circe/codecs/semiauto-derivation.html
API • in - org.scalamsi.http.QueryRoutes (2 routes) - org.scalamsi.http.CommandRoutes (3 routes) assign them to val routes::HttpRoutes[F]. See examples in the code • in org.scalamsi.Module, instantiate QueryRoutes and CommandRoutes • assign both to val routes: HttpRoutes[F] using below code to combine them: Router(apiPrefix -> (qr.routes <+> cr.routes)) Check Task2 via test: sbt "testOnly *Task2Test" Docs: https://typelevel.org/cats/typeclasses/semigroupk.html
implementation This technique is called - Tagless Final See more in this short and nice blogpost: https:// www.basementcrowd.com/2019/01/17/an-introduction-to-tagless-final-in- scala/
to the Repository level and doing a few input parameter validation. 1. Complete selectAll method implementation by flatMapping the sorting field and calling the selectAll on the repository. Try to use for-comprehension here 2. Implement insert and update method: use existing validate method and then call Repository method to insert or update, if the input data is valid 3. in Module.scala, instantiate real TripService and assign it to val service. Check Task3 via test: sbt "testOnly *Task3Test”
define two more types inside the object refined: 1. A type for JdbcConfig#maximumPoolSize as Int range from 1 to 100. See example of ConnectionTimeout type 2. JdbcConfig#url property, using regexp: jdbc:\\w+://\\w+:[0-9]{4,5}/\\w+ Example: type MyType = String Refined MatchesRegex[W.`""" REG EXP HERE """`.T] b) for properties: driver, user, password use non empty String type c) for connection timeout use the existing type, which is already defined as reference d) in Module.scala, fix the parameters of Transactor.fromDriverManager, by accessing values of every parameter using .value mehtod on each of them. For example: cfg.driver.value Check Task4 via test: sbt "testOnly *Task4Test”
FROM trips WHERE id = $id" .query[Trip] .option .transact(xa) select: doobie is a pure functional JDBC layer for Scala and Cats. It is not an ORM class TripRepository[F[_]: Sync](xa: Transactor[F]) extends Repository[F] import doobie._ import doobie.implicits._ Docs: https://tpolecat.github.io/doobie/docs/03-Connecting.html
val cs = IO.contextShift(ExecutionContext.global) resources: F[_]: Async: ContextShift A Transactor is a data type that knows how to work* with database example for cats-effect IO Docs: https://tpolecat.github.io/doobie/docs/14-Managing-Connections.html
a constant query and then use it inside the TripRepository#selectAll. After calling selectAllQuery call .stream then .drop, . take, compile, to[Seq] and finally transact(xa). In such way, we applied page and pageSize parameters to return a particular offset from the database using streaming approach. 2. in TripRepositoryQueries#updateQuery, write a query by concatenating TripRepositoryQueries#updateFrag with a query fragments for “values” and “predicate”. Use ++ method for concatenation and call .update on the result Check Task5 via test: sh start-dev-db.sh && sbt "testOnly *Task5Test”
- Scalatest + testcontainers-scala - to test integration - path: HTTP Routes Application Postgres Container - Note libraries like Mockito can be usefull as well, but it often it is OK to write mock manually. Thanks to composition of Scala programs and FP
val req = Request[IO](method = Method.GET, uri = getUri(s"/${lisbon.id}")) val res = routes.run(req) implicit ev: EntityDecoder[IO, Trip] res.as[Trip].unsafeRunSync should ===(lisbon) import cats.effect.IO import org.http4s.EntityDecoder val lisbon = Trip(3, "lisbon", Vehicle.Bike, 2000, completed = false, None, None) create request & run it via routes pass decoder to convert the response no test kit is needed, pure FP
image can be used for tests - provides common containers for databases and Selenium - integrated with Scalatest using 2 traits: ForEachTestContainer, ForEachTestContainer Test lifecycle: Start container After Start hook Run test(s) Before Stop hook Stop container Docs: https://github.com/testcontainers/testcontainers-scala
= GenericContainer("nginx:latest", exposedPorts = Seq(80), waitStrategy = Wait.forHttp("/") ) Testing Application with DB waitStrategy can be customised to wait for a specific port, docker health check or a particular line in the log it should “call nginx” in { val port: Int = container.mappedPort(80) val ipAddress: String = container.containerIpAddress …. } override val container = MySQLContainer() // or use one of the few predefined containers Docs: https://www.testcontainers.org/features/startup_and_waits/
or ForEachTestContainer trait and override container property with out-of-the-box Postgres container class. Set image name to postgres:10.4 2. use container property to initialise dbProps map 3. Note: mod variable should be used either lazily or be used inside the test case code block 4. in Module.scala, remove overriden createSchema() method in val repo implementation. We need to use real method, not a stub Check Task6 via test: sbt "testOnly *Task6Test”
given classes, using values in the enclosing type for constructor parameters, with the help of Scala Macros. trait UserModule { lazy val databaseAccess = new DatabaseAccess() lazy val securityFilter = new SecurityFilter() lazy val userFinder = new UserFinder(databaseAccess, securityFilter) lazy val userStatusReader = new UserStatusReader(userFinder) } trait UserModule { import com.softwaremill.macwire._ lazy val databaseAccess = wire[DatabaseAccess] lazy val securityFilter = wire[SecurityFilter] lazy val userFinder = wire[UserFinder] lazy val userStatusReader = wire[UserStatusReader]} compile-time checked macwire version
function to instantiate all values instead of using “new” operator. It should be around 5 values wired with MacWire Check Task7 via test: sbt "testOnly *Task7Test” Also run previous test to check it is not broken: sbt "testOnly *Task6Test” Docs: https://github.com/softwaremill/macwire
of root module, use another docker image such as “openjdk:8-jre- alpine” and “AshScriptPlugin” to generate “busybox" compatible image shell script. AshScriptPlugin is provided by native-packager-plugin, so just use it Check by building and running Docker image: sbt docker:publishLocal docker stop postgres & docker rm postgres ./start.sh 1 postgres and 1 scala service containers should be started https://www.scala-sbt.org/sbt-native-packager/archetypes/misc_archetypes.html
bump version, publish artefacts, pusg to Git and more 1.2.1-SNAPSHOT -> 1.2.1 -> 1.2.2-SNAPSHOT - provides a customisable release process - interactive and non-interactive mode - versioning customisation
commit all the changes: git add -A && git commit -m "ready for release” 2.2. release current project version and bump version to the next snapshot. sbt- release configuration is already added to build.sbt. Just run below commands to see how the version is increased and what will be in the GIT log as commits, tags Check by running in shell: RELEASE_VERSION_BUMP=true sbt “release with-defaults” git log --oneline cat version.sbt // look at new version RELEASE_PUBLISH=true sbt “release with-defaults” git log --oneline cat version.sbt // look at new version