Playing on Hard
Connascence of Value

Per connascence.io:

Connascence is a software quality metric & a taxonomy for different types of coupling.

I use it as a taxonomy and the mere concept is, to me, of huge help in both building and maintaining systems. Understanding and actively tracking connascence surfaces dependencies between components that sometimes never even touch directly. I use it to track values, names and algorithm dependencies between components written in different languages and that are developed in different development lifecycles.

For now I will just focus on the connasence of values.

Connascence of value

In the age of distributed, heterogenous software, connascence of value is like magic numbers on steroids:

  • You still have to change the same value in multiple places in your code base, just like with magic numbers
  • These places may be “far away” (in different repositories) lowering the chances of finding all the instances
  • The values may be part of different languages which encode them differently, lowering further the chances of finding all instances
  • Since the components are coupled, even if they never touch directly, they need to either be released together or in a backward compatible order (which isn’t always possible)

Solutions

As with everything else in software development, you can solve connascence of values either during development (static) or during runtime (dynamic).

Dynamic solutions

A common dynamic solution in distributed systems is to use something like etcd which replaces the connascence of value with a connasence of name. That is: where before the components had to “agree” on a certain value, now they have to agree on two names:

  1. the name of the central component returning values for names (aka key/value store) and
  2. the name (key) of the value in this central component

However, since the changes are dynamic, each component how has to know how to fetch the values as well as how to handle a change of configuration midprocess. This is easier said than done and from what I have seen it’s a common source of bugs. Furthermore, now all your components depend on another component and also have to be robust to slow or partitioned network whenever they are requesting a config change. Not to mention that components are likely to have to be updated in a particular oder rather than all at once (the so called connascence of execution)

A simpler approach, and one that is easier to make robust, is to build components that treat configuration as immutable during its runtime. That moves the problem of the updates to overall deployment orchestration which will be (hopefully!) the same for all components. One way to approach this is to use the same common key/value store but dynamically inject static configuration into target components, usually via the environment variables.

For example, imagine a Kubernetes service that subscribes to etcd and that on received changes redeploys a set of all the other Kubernetes services that depend on the updated values. You would have to agree on names and for the sake of ease the names woyld have to be globally unique: that allows discovering all services that use the name in their environment variables and updating them without a central directory and a map of global to component specific names. But also notice that by adopting a convention, you introduce a connascence of algorithm of the said convention: every component needs to have the same way to load its “static” configuration. Still preferrable but if you ever want to changed it, you will again have to change all the components.

Update: see why this is also flawed due to connascence of execution)

Static solutions

A fully static approach could be done leveraging, once again, a common key/value store which would be queried during the build time. The build process would then include these values into the rest of the source code. This is potentially insecure - there are values that you may never want to share outside of your org, not even on your CI/CD, and making such sharing easier is just asking for trouble (make unsafe things hard and you will have less problems). But the things it solves are significant:

  • Your builds failing, say due to a bad value, would never be as bad as your production system failing
  • If the key/value store is down or is slow, then the build fails or is slow, but it’s not your production that needs to handle such issues.
  • Since key/value store lookup is now part of the build process, which is usually specific for each component, the global name can be easily mapped to a component-only name (so there is no connascence of algorithm in the components)
  • Finally, you can even output the values in whatever encoding is best suitable for the component being built.

Code solution

At the end I have to also mention a solution based entirely on the code: including the (safe) values into the source code and checking that in with common comments and tags. It cannot be used for secrets but otherwise it’s a good place to start. If you couple it with reading all such configuration through environment variables that have globally unique names… you are just a few steps away from automating the updates of the environment variables during the runtime. And that I think is probably the most optimal solution for most cases.

Regarding comments, I usually comment all such values (but also names and algorithms) with “connascence of x with component y”. The word “connascence” is great: it has a unique spelling and is rarely used in real life. But that unique spelling is also it’s weakness: it’s too unique and may lead to bad spelling which in turn makes connascence harder to find or tally.


Last modified on 2021-05-07