ZIO/ZLayer FAQ: How do I use a Typesafe Config HOCON properties file with ZIO?
Solution
For this ZIO ZLayer
solution, you can use the zio-config library for things like this, but at the moment my preferred approach is to hand-code this solution. That’s probably because I’ve written code before to read a HOCON file, so it’s more straightforward atm.
Typesafe Config HOCON file location
Before we get into the solution, note that a Typesafe HOCON configuration file needs to be somewhere on your application classpath. For instance, if you’re running your application using SBT, the HOCON application.conf
file needs to be in the src/main/resources
directory (or somewhere similar). If the file isn’t in the right location, you’ll see error messages like this:
com.typesafe.config.ConfigException$Missing: system properties: No configuration setting found for key 'myapp'
Back to the solution
Getting back to the solution ... given this Typesafe HOCON properties file named application.properties
:
myapp {
username = "alvin"
email = "coyote@acme.com"
}
The following ZIO 2 code shows how to read that HOCON file when it’s located where Typesafe wants it to be located. I’ve added a lot of comments to the code, so please see the comments and Scala code for more information:
package _zlayer_hocon
import zio.{Console, Task, ZIO, ZIOAppDefault, ZLayer}
import com.typesafe.config.{Config, ConfigFactory}
// these fields correspond to the HOCON properties file fields
case class AppProperties(username: String, email: String)
// read the HOCON file into a Config instance
def readHoconFile(filePath: String): Task[Config] = ZIO.attempt {
val config: Config = ConfigFactory.load(filePath)
config
}
// given a Config instance, create an AppProperties instance
// from those properties
def createAppPropertiesFromProperties(config: Config): Task[AppProperties] = ZIO.attempt {
val username = config.getString("myapp.username")
val email = config.getString("myapp.email")
AppProperties(username, email)
}
// this object contains the ZLayer that opens and reads the HOCON
// configuration file, and populates an AppProperties instance
// based on that configuration information.
object HoconConfig:
def live(filePath: String): ZLayer[Any, Throwable, AppProperties] = ZLayer {
for
properties <- readHoconFile(filePath)
appProperties <- createAppPropertiesFromProperties(properties)
yield
appProperties
}
/**
* This is the "main" ZIO 2 application, including an `app` value
* and the required `run` value. The `run` value *provides* the
* configuration information to the `app`.
*
* For ZLayer info, see:
* - https://zio.dev/reference/contextual/zlayer
*
* Note: Can also use zio-config for app configuration.
*/
object ZioZLayerHoconFile extends ZIOAppDefault:
val app: ZIO[AppProperties, Throwable, Unit] = for
appProps <- ZIO.service[AppProperties]
_ <- Console.printLine(s"USERNAME: ${appProps.username}")
_ <- Console.printLine(s"EMAIL: ${appProps.email}")
yield
()
val run =
app
// must be in 'src/main/resources'; does not work with absolute file location
.provideLayer(HoconConfig.live("application.conf"))
.foldZIO(
failure => Console.printLineError(s"FAILURE = $failure"),
success => Console.printLine( s"SUCCESS = $success")
)
When this ZIO 2 ZLayer
application is run, you should see output like this:
USERNAME: alvin
EMAIL: coyote@acme.com
SUCCESS = ()
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Notes
One note I’ll add is that the readHoconFile
and createAppPropertiesFromProperties
functions can be combined into one function. I just show them separately to make the live
function in HoconConfig
a little more interesting.
Also, another interesting and unique part of this solution is that it shows how to pass a parameter into a ZLayer
from inside the run
value. By that, I mean that the ZLayer
needs to know the name of the configuration file, and I pass that parameter into the live
function inside the run
value. At the moment you can’t find solutions like this on the internet, so I hope this is helpful.
As noted, I have a lot of comments in that code that describe how things work, so please see those comments for more information.