Let's play a game
Just some of the horrors of frameworks
A set of architectural patterns for building distributed systems
We can do better
Some examples may contain traces of Spring.
If you're allergic, please close your eyes.
vs
@Configuration class MyConfig {
@Bean DataSource dataSource(){
//some side-effecty things allocating some DB connection pool
}
@Bean UserRepository userRepository(DataSource dataSource) {
return new UserRepositoryImpl(dataSource);
}
}
vs
fs2.co
NPE when called
Lack of side effects
Lack of referential transparency
for any expression `a`
given `x = a`
All occurrences of `x` in a program `p` can be replaced with `a`
//Example
val prog1 = (x, x)
val prog2 = (a, a)
//both sides equivalent
prog1 <-> prog2
Manages resource acquisition and cleanup
Acquiring lock1
Acquiring file reader: .gitignore
Acquiring lock2
Finished reading lines
Releasing lock2
Releasing file reader: .gitignore
Releasing lock1
.idea/
*.iml
*.iws
*.eml
out/
type HttpRoutes = Request => Response
type HttpRoutes = Request => IO[Response]
type HttpRoutes = Request => IO[Option[Response]]
type HttpRoutes = Request[IO] => IO[Option[Response[IO]]]
//Kleisli[F[_], A, B] ~= A => F[B]
//OptionT[F, A] ~= F[Option[A]]
type HttpRoutes =
Kleisli[OptionT[IO, ?], Request[IO], Response[IO]]
type HttpRoutes[F[_]] =
Kleisli[OptionT[F, ?], Request[F], Response[F]]
If a route is just a function
...then we can modify its input and output
Example: Response timing
Built-in server middleware (0.20.0-M5)
What about clients?
Just another function
Built-in client middleware (0.20.0-M5)
Creating clients from routes - trivial server stubbing
Calling endpoint in test = calling a function
case class Country(name: String, capital: String)
def countryById(id: CountryId): IO[Option[Country]] =
sql"""select c.name, c.capital
from countries
where c.id = $id"""
.query[Country]
.option
.transact(transactor)
Transactor
Query
def countryById(id: CountryId): Query0[Country] =
sql"""select c.name, c.capital
from countries
where c.id = $id""".query[Country]
val countries: Query0[Country] =
sql"select c.name, c.capital from countries"
.query[Country]
val country1: ConnectionIO[Option[Country]] =
countryById(CountryId(1L)).option
val countriesStream: Stream[ConnectionIO, Country] =
countries.stream
ConnectionIO -> IO
transactor.use { xa =>
country1
.transact(xa)
.flatMap(putStrLn(_)) // Some(Country(...))
}
End-to-end streaming with http4s
Query typechecking
class AnalysisTestScalaCheck extends FunSuite with Matchers with IOChecker {
val transactor = Transactor.fromDriverManager[IO](
"org.postgresql.Driver", "jdbc:postgresql:world", "postgres", ""
)
test("countryById") { check(countryById(CountryId(1L))) }
test("countries") { check(countries) }
}
Slides: https://git.io/fhsW5
Code: https://git.io/fhoVH
Get in touch
(Read my blog! blog.kubukoz.com)