Qualifiers and Configuration Coordinates in Configuration

This post is part of a larger series, I guess, maybe, that is heading toward what a good Java configuration API might look like.

(My usual disclaimers: I work for Oracle on the Helidon project, but never speak for the big red O unless I have to (this is not one of those times). I am also a committer on MicroProfile Config (but have reservations about the project) and Jakarta Config (and have reservations about its direction). I’m not writing here in any of the roles listed above (I rarely do).)

I’ve written before about how Java configuration, reduced to its essence, when viewed from an application developer’s standpoint, has nothing to do with dynamic typing or named properties or converters or tree nodes or all the other things that people immediately think of that look and smell like System properties for no good reason. Instead, it must be a low-level, deliberately limited precursor to things like dependency injection systems (since it will presumably be used to configure them). Precursor or no, it is still about asking for a particular kind of object suitable for a particular kind of application running in a particular kind of place and receiving it. Full stop.

I’ve also written about how a great example of such a configuration system API, as utterly straitjacketed as it is (on purpose), is java.util.ServiceLoader: pass it a type, get back an object that was configured in a place that doesn’t require code to be recompiled to reconfigure.

So what does suitability mean here? What is a place? What does it mean to have an object suitable for an application? Isn’t there just one application?

I like to think of this in terms of configuration space. Configuration space is a {emulates Carl Sagan} huge multidimensional void that contains all possible deployments of your application. When you deploy your application, you are, whether you realize it or not, situating it in configuration space.

I will occasionally call the the various points along configuration axes that you use to identify your application in configuration space configuration coordinates. They’re like the latitude and longitude that pick out your application in configuration space.

Other times I will call these coordinates qualifiers, after CDI’s qualifiers: they are additional ways to further describe whatever it is you’re talking about. Acquiring a Car may logically require that you specify exactly what kind of Car implementation you’re looking for, i.e. a suitable one that is qualified by qualifiers you supply.

Dependency injection systems, as noted, almost all have the notion of qualifiers. The only real difference is that most dependency injection systems begin by assuming that if you have an unqualified injection point, you are asking for the default thing that might satisfy the injection point.

Configuration is different. An application is implicitly qualified, logically speaking, by the configuration coordinates that describe its location in configuration space. If I run my application in the test environment and in some specific cloud region with experimental features turned on, the application, as developed by the application developer, really shouldn’t have to change at all to ask for a different thing (or else we would not, by definition, be talking about configuration!). So if the application developer is asking for a Car, she should continue asking for a Car, i.e. she shouldn’t have to recompile her code just because her application is in a different “place”.

That would seem to contradict the statement earlier that acquiring a Car may require that you specify exactly what kind of Car implementation you’re looking for. You can work around this conceptually by saying, OK, the configuration system, when it comes up, must first figure out what the implicit qualifiers of the application it “belongs to” are. That is: qualifiers are primordial things to configuration systems.

Consider Java’s own ResourceBundle, which was not really designed to be a configuration system, but which does acknowledge the primordial nature of implicit qualifiers. When you ask for a ResourceBundle, the underpinnings of the system figure out what your application’s coordinates are in resource bundle space, and load a resource bundle for you that is suitable for where your application finds itself. Less stuffily: if the current locale is German, then you’ll get the German resource bundle. If it’s unset, then you’ll get the default one. Your code doesn’t change. Locale, in this case, is an example of a qualifier that is implicit and locates your application in a particular kind of space.

Consider Spring, which features configuration profiles, a clumsy and coarse-grained way to give a name to logical bundles of qualifiers. Ask for a particular thing, and depending on what profile your application has, you might get the profile-specific version of that thing or the default one. The developer of the application doesn’t change her code.

Consider Netflix’s Archaius, which allows you to define a CascadeStrategy, under the covers which is a rather clumsy way to emulate a ResourceBundle.Control object. Same deal; your application ends up being located in configuration space by a mechanism that you don’t really have to think about as an application developer: if you ask for a particular configuration item, then the one that is most suitable for your application’s “location” will be given to you, without your code having to change.

Many of these configuration or configuration-like systems that work with qualifiers, whether they know it or not, also tend to treat them as hierarchical, and bake them into the configuration names. Obviously, qualifiers need not be hierarchical, and equally obviously do not change the fundamental name of the thing they qualify, but, oddly, this became convention at some point, and so this is how they are often modeled—a practice which obscures what is really going on. For example, in English, if you’re looking for a hostname, even if you’re looking for one suitable for the development environment, you’re still looking for a hostname, not a dev.hostname.

If you stretch all of this to the breaking point, then it’s also the case that a name of a particular configuration item is just another qualifier! If you’re looking for a Frobnicator named goop, then you’re looking for a Frobnicator with qualifiers of name=goop, environment=development (let’s say), experimentalFeatures=on (let’s say), and so on. But a name is as good a qualifier as any to elevate to principal status, so there’s no need to go crazy here.

So then: we have at the core of our configuration concepts types (the kinds of things the developer is trying to acquire from configuration) and qualifiers (the configuration coordinates identifying the application in configuration space), as well as names (which are really just an obvious elevated kind of qualifier).

This should sound familiar. Any dependency injection heads out there will recognize that types and qualifiers are exactly what you use in every dependency injection system on the planet to retrieve objects. Most DI systems will represent them as Java annotations, but if you dig into them you’ll see that that is mainly for the strict immutability semantics that annotations happen to carry with them, not for anything that is somehow magic about annotations per se.

Finally, there is another Java-centric system out there that does all of the following:

  1. Allows the application developer to request a qualified Java object of a particular type
  2. Allows the application developer to further qualify that request (and actually qualify individual pieces of a request)
  3. Allows the application developer to supply representational hints about the Java object being returned
  4. Mediates between potentially competing result producers to deliver the result that is the most suitable for the caller’s “location” in “space”

That system is JAX-RS/Jakarta RESTful Web Services! Obviously I’m not suggesting that Jakarta RESTful Web Services is a good configuration system (for one thing, it is too high-level and the concepts involved are at the wrong architectural level). But I am suggesting that the fact that it deals with paths to Java-typed resources, qualified by header values, media types, locales and all the other implicit coordinates that locate an application in space is worth looking at in the abstract. I hope to sketch that out in another post.