One thing, we all have to deal with during writing applications is configuration.

Photo by Michał Parzuchowski on Unsplash

Regardless of architecture and size we always have to deliver params from .conf file to service or factory method. Passing those values can be implemented in numerous ways with or without some headaches. No standard regarding this issue causes the situation where we encounter many different ways of extracting configuration in our applications. Due to increased noise/information ratio in our services, our fellow developers have a harder time understanding our code.

Goal

In this post, I will share with you a few simplified solutions from my own experience as well as my thoughts about them. Then we will try to improve them at least a bit.

Within Scala community Typesafe config is standard (easily used also within Java applications) so I will treat this as our base. I hope, after this reading, you will no longer pollute your business logic with configuration parsers. Let’s start!

To present it clearly let’s assume we have some services in our application which handle the external connection. To do so, it needs an URL as well as a port. To keep it simple, in my examples, I’ll print passed values straight to the console.

Setup

Here is the basic setup:

Case 1

We will start with a simple case where we pass Typesafe config to service which will handle extracting all the values it requires. In that case, we have two ways of doing so:

as static values

inside method whenever needed

What are the advantages here?

Simple and easy

Good for prototyping

The other side of the coin

Handling configuration is fragmented

How about a situation where param value is not standard and needs some programming? Where should this kind of logic live?

Reading values in methods push possible errors to the last feasible moment, call site.

It could result in strange errors in case of rarely visited use cases.

Including config parsing inside service violates the Single responsibility principle.

A bigger application could suffer from increased noise/information ratio. It raises maintenance costs.

Increases Wtfs/Minute measurement

Case 2

Problems listed above are hardly negligible. That’s why many people introduce case class typed config representation.

Let’s define those.

When that’s done we need a parser:

Awesome, now we need to adjust service startup a little:

The last thing we change is our service:

What have we gained by this?

Service lost excessive responsibility

All places using params are now informed about changes at compile time

We have achieved responsibilities separation

We created proper space to handle non-trivial config params

The drawback here is increased verbosity due to the creation of extra classes.

Case 3

Nevertheless, there is a way to deal with boilerplate. Here is where pureconfig library comes in handy.

Pureconfig allows us to express configuration as case classes if we follow some simple rules.

Nesting expresses a case class field path, with hacoon this is { and .

and Dash(-) in config files transforms into camel case in field names so config-param becomes configParam

So now let’s remove our manual config parsing and use pureconfig. Predefined case classes stay the same.

Leaving case classes the same, let’s take leverage of pureconfig. Main class changes once again:

This way, without excessive code, we have parsed and provided configuration to the service.

What is worth mentioning is pureconfig dependencies, which aren’t many

typesafe-config — so now it comes as a transient dependency if you wish so

shapeless — yes, this is an elephant in the room, it’s hidden in library code so you can still keep your app shapeless free

Case 4

Some of you might ask: Can I parse single config file into multiple case classes?

Answer: Yes! All of this works pretty well with parsing single config file into many ADT’s.

Here is such an example

Case 5

Ok, some might say: Most of the projects out there aren’t that simple! A lot of them uses play with guice to implement web api. Is it still possible to apply it under those circumstances?

Yeah, sure, and it’s rather straightforward. To show this I had to prepare an endpoint serving url with a port from our services:

And the controller:

The single tricky spot is to use pureconfig in the appropriate file. Modules.scala is the guy we are looking for. About guice wiring, all we have to do is to provide an instance of a configuration case class.

Case closed!

Summary

The final call on whether to use this style or not is up to you and your teammates. Benefits of such an approach should make you at least consider gradual migration toward simplification. Especially with an almost nonexistent cost. In case you have already somehow been parsing your config to case classes the cost is even less and consists mostly code deletion. As the final note, I would love to mention that I believe that every approach making your code more readable, simpler or less responsibility dense is worth a discussion or two.

It’s only the tip of the iceberg. My goal here was to present simple, easily implementable improvement. That’s all, without any fancy things that you could introduce later. And how about your experience regarding the config? How are you dealing with it? Maybe you use an entirely different solution? Please, share your experiences!

Code link: code