github zio/zio-config v2.0.0-M1

latest releases: v4.0.2, v4.0.1, v4.0.0...
2 years ago

What's Changed

|@| disappears and use zip, which returns flattened tuple

 
 
final case class MyConfg(age: Int, name: String)

val config: ConfigDescroptor[MyConfig] = 
  int("AGE").zip(string("NAME")).to[MyConfig]

// and not, (int("AGE") |@| string("NAME"))(MyConfig.apply, MyConfig.unapply)

val config: ConfigDescriptor[(Int, String, Double)] = 
  int("AGE") zip string("NAME") zip double("AMOUNT")

// implies, no more `.tupled` functionality that worked with `|@|` to retrieve tuples
    

Apply and Unapply is no more required and supported. Use .to[CaseClass]

final case class MyConfg(age: Int, name: String)

val config: ConfigDescroptor[MyConfig] = 
  (int("AGE") zip string("NAME")).to[MyConfig]

// and not, (int("AGE") |@| string("NAME"))(MyConfig.apply, MyConfig.unapply)

ConfigSource return types are simplified, and now more composable with ConfigDescriptor

ConfigSource.from will not return ZLayer or ZIO anymore. This means we have more easier composability with ConfigSource regardless of the type of ConfigSource.

  import zio.config._, typesafe._
  
  val source = ConfigSource.fromMap(Map("a" -> "b")) orElse ConfigSource.fromHoconFile(file)
  
  read(config from source)

read returns ZIO and not Either

Now on read(config from source) returns a ZIO[Any, ReadError[String], Config] instead of Either[ReadError[String], Config]

This implies, there isn't a need of doing ZIO.fromEither if you happen to use read in a ZIO context. Removing it should make things work.

  val app: ZIO[Any, ReadError[String], (Int, String)] =
    read((int("age") zip string("name")) from ConfigSource.fromMap(Map.empty))

Better semantics around resource handling for ConfigSource

ConfigSource internals have been changed to support the fact that forming a ConfigSource involves an ZManaged effect.
More details further down below.

ConfigSource is now just a function (a Reader), and initialisation of Reader can be memoized

zio-config can memoize the initialisation config-source per key in your config. Example: Reading 5 config values to summon a case class instance can spin up a new "connection" 5 times, unless we memoize this initialisation.

If there is a resource acquisition and release involved in reading each config parameter, then acquire and release will happen 5 times as well. We can memoize ConfigSource by calling .memoize, and in this case the resource acquisition and release happens once, with an in-memory tree representing your config for further reads.

More details

This is mainly because, at the core, ConfigSource is a Reader, which is a function f: PropertyTreePath[K] => IO[ReadErrror[String], PropertyTree[String, String]. However there is more to it, the Reader itself is wrapped in an ZManaged effect. Infact, there is more to it, where its a double layered ZManaged to allow memoization of the effect (ZManaged) to even form this function.

An excerpt from the original code

    type Managed[A]              = ZManaged[Any, ReadError[K], A]
    type TreeReader              = PropertyTreePath[K] => ZIO[Any, ReadError[K], PropertyTree[K, V]]
    type MemoizableManaged[A]    = ZManaged[Any, Nothing, ZManaged[Any, ReadError[K], A]]
    type ManagedReader           = Managed[TreeReader]
    type MemoizableManagedReader = MemoizableManaged[TreeReader]

As a user, probably this is more of heavy lifting details, which you would like to forget. Most of the sources as of now are memoized for it's initialisation per read. This is mainly because the underlying pre-built sources are not quite complex yet. For example: We don't have database table as ConfigSource which requires acquiring a java.sql.Connection to even form the function PropertyTreePath[K] => ZIO[Any, ReadError[K], PropertyTree[K, V]].

For those who are curious, take a look at the implementation of a ConfigSource using a dummy PropertyTree in the below example.
In here, app results in printing out Acquiring resource and Releasing resource twice, since there are two
keys that's being retrieved.

import zio.config._, ConfigSource._, ConfigDescriptor._

val dummySource                                                   =
    PropertyTree.Record(Map("age" -> PropertyTree.Leaf("1"), "name" -> PropertyTree.Leaf("afsal")))

  val effectFulReader: ZManaged[Any, ReadError[String], TreeReader] =
    ZManaged
      .make(ZIO.debug("Acquiring resource"))(_ => ZIO.debug("Releasing resource"))
      .flatMap(_ => ZManaged.succeed((path: PropertyTreePath[String]) => ZIO.succeed(dummySource.at(path))))

  val source: ConfigSource =
    ConfigSource.Reader(Set(ConfigSourceName("my source")), ZManaged.succeed(effectFulReader))

  val app: ZIO[Any, ReadError[String], (Int, String)] =
    read((int("age") zip string("name")) from source)
    
 // [info] Acquiring resource
 // [info] Releasing resource
 // [info] Acquiring resource
 // [info] Releasing resource
 // [info] (1,afsal)  
    

Now if source is memoized it will acquire and release only once.

  val app: ZIO[Any, ReadError[String], (Int, String)] =
    read((int("age") zip string("name")) from source.memoize)
    
  // [info] Acquiring resource
  // [info] Releasing resource
  // [info] (1,afsal)

ConfigSource initialisation per read and multiple reads

There is a second level of memoization where config-source can be initialised once and for all, and is reused in multiple reads. This memoization is done by calling configSource.strictlyOnce returning a ZIO effect indicating that the communication with the actual data source happens prior to the reads.

import zio.config._
import zio.config.typesafe._
import zio.ZIO

import ConfigDescriptor._

Example, in the below case, it will hit the actual file twice.

  val source =
    ConfigSource.fromHoconFilePath("/Users/afsalthaj/sample.txt")

  val app: ZIO[Any, ReadError[String], (Int, String)] =
    for {
      age  <- read(int("age") from source)
      name <- read(string("name") from source)
    } yield (age, name)

However in the below case, it will involve talking to the actual file only once

  val source =
    ConfigSource.fromHoconFilePath("/Users/afsalthaj/sample.txt")

  val app: ZIO[Any, ReadError[String], (Int, String)] =
    for {
      src  <- source.strictlyOnce
      age  <- read(int("age") from src)
      name <- read(string("name") from src)
    } yield (age, name)

Integration with Enumeratum - an experimental module

https://github.com/zio/zio-config/blob/master/enumeratum/shared/src/main/scala/zio/config/enumeratum/package.scala#L9

Unify API for ConfigSource

With right set of imports, example import zio.config.typesafe._ or import zio.config.yaml._, you can now use ConfigSource.from... instead of being a bit inconsistent by specifying TypesafeConfigSource.from..

import zio.config._
import zio.config.typesafe._

ConfigSource.fromHoconFilePath(..)
ConfigSource.fromMap(..)
ConfigSource.fromSystemEnv()

Zoom into ConfigSource using PropertyTreePath - an experimental feature.

We can now peek into a subtree of a source and make it ConfigSource, and thereby reduce redundant case class configurations, especially useful when dealing with auto-derivations where your case class is not corresponding to the original source.

https://github.com/zio/zio-config/blob/master/examples/shared/src/main/scala/zio/config/examples/typesafe/SubConfigExample.scala

import zio.config._, typesafe._

 {
    "a" : {
        "b" : {
            "c" : [
                {
                    "x"  : 1
                    "y" : 2
                }
                 {
                    "x" : 3
                    "y" : 4
                }
             ]
         }
     }
 }       


final case class ShortConfig(x: Int, y: Int)

read(descriptor[ShortConfig] from ConfigSource.fromHoconFile(file).at(path"a.b.c[0]"))

// returning ShortConfig(3, 4) 

We have seen users struggling to zoom into ConfigSource since the original source may not allow this when it comes to details. Example: lightbend/config#30. Hopefully this feature will be useful for the direct users of typesafe/config too.

There is still more experimentations done at this level.

New way of Layer

https://github.com/zio/zio-config/blob/master/examples/shared/src/main/scala/zio/config/examples/LayerExample.scala

https://github.com/zio/zio-config/blob/master/core/shared/src/main/scala/zio/config/ConfigModule.scala#L75

Dependency Updates

Don't miss a new zio-config release

NewReleases is sending notifications on new releases.