7mind / izumi

Productivity-oriented collection of lightweight fancy stuff for Scala toolchain
https://izumi.7mind.io
BSD 2-Clause "Simplified" License
615 stars 66 forks source link

Automatic resolution of shared component conflitcs caused by per-role config conflicts #2038

Open pshirshov opened 11 months ago

pshirshov commented 11 months ago

Currently we may have effective role incompatibilities caused by the need in config variations. We may have a shared component S requiring config section C and two roles, R1 and R2. If these two roles need to have some value in C set to different/conflicting values we are screwed.

Current state

Currently we can only alleviate this problem by redesigning our application in order to avoid configuration conflicts.

For example:

Instead of having

role1.conf:

postgres = { uri = "..."}
role2.conf:

postgres = { uri = "..."}

We may write

shared.conf:

postgres = { 
role1 = {uri = "..."}
role2 = {uri = "..."}
}

Then we'll need a smart connection component which would reuse underlying connection for the same URIs.

Desired changes

We might automate this.

The key idea is simple:

  1. While we trace the object graph, we should look for configurable components
  2. When we find such a component, we should check which roles retain it
  3. We should load role configs individually and check if there are conflicts in the corresponding sections. If there are no conflicts we may configure the component straight away. If there are conflicts, we should create multiple copies of the rest of the graph, one per conflicting version.

We can definitely do the splits because we know that the graph has specific shape: generally there are NO dependencies between the roots.

I can't see a way to fit this into existing tracing pass, so probably this should be done as a separate pass which happens right after semigraph resolution.

neko-kai commented 11 months ago

By doing this we'd lose the guarantee that all our components are singleton and make things too complex / untraceable. As for reading from a role config instead of shared config, we could make this explicit:

makeRoleConfig[PostgresConfig](Role.id)("postgres")