Case study: The digital transformation of Santa’s logistical nightmare - Part 2
In this blog post, we’d like to go through how Santa’s workshop makes use of cats-effect (CE3) to bootstrap itself.
You can check out the rest of the series:
- Part 1: Introduction
- Part 2: This post
- Part 3: F[F[S]] Kafka! fs2-kafka
- Part 4: http4s
Before we get started, we want to say that the Typelevel documentation on cats-effect is very complete and well written - so we strongly recommend reading through those if you haven’t had any exposure to cats-effect.
With that out of the way, let’s get our paws wet and see how we can leverage CE3 to wrangle together all these side effects in our
One of the first things you’ll see is that this isn’t a regular scala
App but instead extends
This provides us with an entry point to our application which will evaluate an
IO[Unit] that we give it.
It is also capable of handling interruption signals by cancelling our application and releasing resources.
If you need access to either program arguments or the ability to return a specific exit code - you’ll need to look at the standard
Carrying on to the definition of
loadKafkaConfigFromEnv (see below) of
Main.scala, we have some code that loads the necessary environment variables and if not those values are not there the whole application will fail with a
MissingConfig error. We also want the return type of this definition to be
IO[KafkaConfig]. A more common approach for production apps and configuration values is to use libraries such as Ciris or PureConfig.
To read environment variables we use
Env[IO] which is part of CE3 and it’s there for that very reason, pretty handy. On the second and third lines of the code above, we have the two
Option[String] environment variables that we need to combine to complete our configuration - here you can see us using
mapN to combine the options. We want our app to fail bootstrapping if the configuration is missing, so we use
liftTo to convert
Option.None into an
Coming back to the definition of
run, the last thing we want to do is initialise our application’s resources and run it forever until it receives an interrupt.
We do this by passing our configuration to
SantaServer.resource which returns a
Resource[IO, Server]. We allocate our resources and allow the program to run until terminated by calling
useForever on the
Resource(There will be more on using Resource in other posts). One minor thing to note here, is that we call
.unit afterwards - this is because
useForever has a return type of
Nothing to indicate that the code will never return normally and this triggers a dead code warning from the compiler. We could refactor the code or use a no warning annotation here, but we opted for
.void to change the type to
As part of the application shutdown, finalizers run for the resources we initialised. This means all resources related to Kafka Consumers, Http4s Server, etc are all cleanly terminated… which is Nice. You’ll likely run into
Resource quite often and it is talked about in the CE3 documentation we mentioned earlier, so if you’d like to learn more we’d recommend checking it out!
Soon after starting we had to decide what style to write our application in. You’ll frequently come across a pattern called ‘Tagless Final’ where implementations are expressed with an abstract
F[_] effect type and dependencies commonly injected as context bounds.
We chose not to do this and to write our code directly against
IO[_] to make the examples more accessible.
In our example you will see IO as a type parameter to many traits. Some of them defined by libraries (like in the case of Env) and some are defined by us, type parameters work as usual and also they are sign of how we want to deal with all side effects.
You made it all the way to the end!! wooo! Here is a picture of us! (the authors of this post!!)
Dog & Bones