Parsing environment variables in kotlin with konfig

July 27, 2021 &english @code #kotlin #konfig

There is a wonderful kotlin library for reading configuration from various sources (environment variables, files, etc.) – konfig. It allows to define your application configuration in a declarative way and even perform some data transformations along the way.

Konfig was used in milestones project to read some values from a predefined .env file and, if absent, from environment variables.

val MILESTONES_JIRA_URL      by stringType
val MILESTONES_JIRA_USERNAME by stringType
val MILESTONES_JIRA_PASSWORD by stringType
val MILESTONES_JIRA_PROJECT  by stringType
val MILESTONES_PAGE_SIZE     by intType

Fallback order can be explicitly specified by overriding combinator.

val env = EnvironmentVariables() overriding ConfigurationProperties.fromOptionalFile(File(".env"))

Usage is pretty straightforward too.

val jiraClient = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  URI(env[MILESTONES_JIRA_URL]),
  env[MILESTONES_JIRA_USERNAME],
  env[MILESTONES_JIRA_PASSWORD]
)

But today, there was a slight requirements change and I decided to factor out explicitly hardcoded mapping (finally) to become actual configuration. So I did transition from this:

fun team(c: String) = "Team $c"

val teamHeads = mapOf(
  team("Salesforce") to "a.person",
  team("Internal Automation") to "a.human"
  ...
)

To the nested definition of “how to parse a value”. Configuration values itself are just comma separated string MILESTONES_DEPARTMENTS_MAPPING="Salesforce:a.person,Internal Automation:a.human". So, inner layer gets converted into Pairs after splitting by ":". Outer layer (after splitting by comma) converts inner list of Pairs to a Map.

val MILESTONES_DEPARTMENTS_MAPPING by
  listType(
    listType(
      stringType,
      ":".toRegex()
    ).wrappedAs { Pair("Team ${it.first()}", it.last()) },
    ",".toRegex()
  ).wrappedAs { it.toMap() }

At the time of usage, env[MILESTONES_DEPARTMENTS_MAPPING] value is already a Map<String, String>. So there is no need to worry about conversions, mapping or any other transformation of the input configuration string, neat! I very much like such declarative approach, it reminds me optparse-applicative library from Haskell ecosystem.