Config Smell
jPOS configuration is very flexible, but that flexibility can sometimes lead to confusion.
We have two main categories:
- Build-time static token replacement (jPOS build target).
- Runtime environments.
Build-time static token replacement
This is described in detail in the Configuration Tutorial, but the basic idea is that at build time, when you call gradle installApp
or gradle dist
, you have the ability to define a compile-time target
.
The default target
is called devel
and reads an optional property file called devel.properties
.
If your devel.properties
file looks like this:
ABC=AlphaBravoCharlie
And then you have a QBean configuration like this:
<property name="ABC" value="@ABC@" />
The @ABC@
will be expanded to AlphaBravoCharlie
in the final file that will land in the build directory.
If you go to build/install/xxx/deploy/yourxml.xml
, you’ll see something like this:
<property name="ABC" value="AlphaBravoCharlie" />
The jPOS build system performs that replacement in XML files in the deploy
directory, as well as in configuration files (.cfg
, .properties
) in the cfg
directory.
A typical use case, very useful during development is to define a database name.
You can have a devel.properties
file with tokens like this:
dbhost=localhost
dbname=jposee
dbuser=jpos
dbpass=password
dbport=5432
And then have a db.properties
file (in src/dist/cfg/db.properties
) that looks like this:
connection.url=jdbc:postgresql://@dbhost@:@dbport@/@dbname@
When you call gradle installApp
, you’ll see that the destination db.properties
file in the build/install/xxx/cfg
directory will look something like this:
connection.url=jdbc:postgresql://localhost:5432/jpos
When you run locally, in development mode, your devel.properties
file, which happens to be the default one, is the one providing tokens for replacement.
But you can call gradle -Ptarget=ci installApp
. In that case, tokens get read from a file called ci.properties
that you can use in your continuous integration environment.
If you have some handy tokens in the devel.properties
file to make configuration tweaks for a development versus continuous integration target, or you have a local
, test
, or stresstest
environment, that’s fine. However, if you have a prod
target, there might be a configuration smell. It can be acceptable, but beware that you are not leaking sensitive information such as database names, URLs, access tokens, etc.
prod.properties
should probably be reviewed by your security team.
Runtime Environments
The runtime environments are explained here, but the basic idea is that you can define a variable either in a YAML file, a Java property, or an OS environment variable and use it as part of the configuration using property expansion. Imagine you have an OS environment variable like this:
export DB_PASSWORD=mysupersecurepassword
Then you can configure a QBean, or config file with something like:
<property name="dbpass" value="${db.password}" />
When you call cfg.get("dbpass")
from your QBean or participant implementation, you will get the string "mysupersecurepassword".
There are variations you can use, such as defaults like ${db.password:nopassword}
, in which case, if the DB_PASSWORD
OS environment variable is not available or not exported, a call to cfg.get("dbpass")
would return nopassword
.
SimpleConfiguration recognizes and de-references properties with the format: ${xxx}
and searches for a system property, or operating system environment variable under the xxx name. As a fallback mechanism of last resort, the property can be resolved from an environment file, in YAML or properties file syntax, found in the cfg directory (default filename default.yml or default.cfg, which can be overridden by the jpos.env system property).
You can add a default value within the expression itself, using the : (colon) separator. For example ${xxx:def_value}
. If the property is not found, the default value will be used.
The format $sys{xxx
} de-references just from system properties, $env{xxx}
just from the operating system environment, and $cfg{xxx}
just from the environment file (the.
In the rare case where a value with the format ${...}
is required, the $verb{${...}}
format (verbatim) can be used.
In addition, a property named xx.yy.zz can be overridden by the environment variable XX_YY_ZZ (note that dots are replaced by underscore, and property name is converted to uppercase.
Things start to get confusing when we hit Config Smell 2:
We often encounter situations where a build-time target configuration looks like this:
dbpass=${db.password}
and then the dbpass
property in a given QBean or configuration file looks like this:
<property name="dbpass" value="@dbpass@" />
That totally works. The resulting file in the build directory after a gradle installApp
will look like this:
<property name="dbpass" value="${db.password}" />
as we want, and then, at runtime, the ${db.password}
value will be de-referenced using the DB_PASSWORD
environment variable.
That's fine, everything works, but why oh why!
When you see a target build time configured like this, new users might get confused about when the ${db.password}
gets de-referenced—whether it happens at build time based on the build-time environment variable or at runtime. It’s very confusing, and even an experienced jPOS developer can get caught off guard. Don’t do it.
We mentioned CODE SMELL 1 and CODE SMELL 2, but there’s CODE SMELL 0!:
Configuration doesn’t belong in the same repository as the source code. That’s why we have a distnc
(distribution, no-config) task in jPOS’ Gradle plugin that only ships the main jar and all dependencies in the lib
directory, leaving the cfg
and deploy
directories to be fetched by other means. We tend to use GitOps for that, even before GitOps was a term; we did this in the old Subversion days. Having a deploy
and cfg
directory in your development repo is handy for development and testing, but we recommend keeping it separate from production.