The server code will require a few new imports in addition to our usual set:
import org.http4s.server.blaze.BlazeBuilder
import scala.concurrent.ExecutionContext.Implicits.global
BlazeBuilder is a server factory, and ExecutionContext will be needed at the moment we start the server. The server is defined as follows:
object Server extends StreamApp[IO] { ... }
StreamApp requires us to implement a stream method, with the solely purpose to produce side-effects and provides cleanup hooks for this stream. This is our implementation:
override def stream(args: List[String],
requestShutdown: IO[Unit]): Stream[IO, ExitCode] = {
val config: IO[Config] = Config.load("application.conf")
new ServerInstance(config).create().flatMap(_.serve)
}
We just read the configuration and delegate the actual server creation to ServerInstance. Let's have a look at it:
class ServerInstance(config: IO[Config]) {
def create(): Stream[IO, BlazeBuilder[IO]] = {
for {
config <- Stream.eval(config)
transactor <- Stream.eval(DB.transactor(config.database))
_ <- Stream.eval(DB.initialize(transactor))
} yield BlazeBuilder[IO]
.bindHttp(config.server.port, config.server.host)
.mountService(new Service(new Repository(transactor)).service, "/")
}
}
Here again we see the same approach: we lift config into the context of Stream, create a transactor, initialize the database, build the repository from the transactor and the service from the repository, and finally mount the service by using the BlazeBuilder factory.
The caller method will then execute the serve method of the server, starting the whole IO program we've built so far.
Now our application can be started and played with. But we want to be sure that it behaves correctly by testing it.