Simple SpringBoot profiles
TLDR SpringBoot profiles, what they are and how to use them simply with flyway example.
Many frameworks have the concept of scoping application settings together around the concept of environments, examples being; dev, test, stage & production. Largely what you scope around is irrelevant but these examples are the most common to scope around.
So what do I mean and how is this useful? Well for local development you probably want the local database credentials in your application properties, you may also have threads turned down or certain services disabled. Whatever is most appropriate to facilitate and accelerate local development for you and your team. Clearly the application settings you run against in production will be different from both performance, debugging and security perspectives and should thus be managed separately.
The 12 Factor Application manifesto also has a great read on configuration management from a different perspective which is well worth your time, here
SpringBoot supports this concept in the form of profiles which are also analogous to the Ruby on Rails runtime environments (see here).
How do I use profiles?
Very simply a SpringBoot's default application properties are specified by src/main/resources/application.properties
. Profile specific properties can be specified in the same file or by a separate file of the following format src/main/sources/application-<profilename>.properties
such as src/main/sources/application-dev.properties
. Profile properties override the default properties in much the same way CSS does. Profile properties do not need to specify all properties, only the ones which you wish to change from the default application.properties
set.
Assuming you have Actuator in your class path (if not, why not?!?), any info.*
properties are exposed through the \info
endpoint. This makes it especially useful for exposing build & release information as well as the profile under which the application is running.
For instance in application.properties
you may have
info.profile=default
spring.jackson.serialization.write-dates-as-timestamps=false
management.context-path=/actuator
and in application-dev.properties
you may have
info.profile=dev
Meaning that when running your application with the dev
profile enabled that the \actuator\info
endpoint will yield something like
{
"profile": "dev"
}
So how do you pass in the desired profile to your SpringBoot application? Very simply;
$ SPRING_PROFILES_ACTIVE=dev gradle bootRun
Just like any SpringBoot property there's a hierarchy down which it searches (see here), so this doesn't have to be a environment variable (useful for a Containerization strategy) but could also be a property in your default application.properties
file or specified some other way.
Flyway example
Recently a problem arose at work where default development data needed to be automatically loaded into all local development environments but only local development environments. Using Flyway to manage database migrations and data assets this became trivial to implement with the help of profiles.
Schema migrations were located in the default resources/db/migrations
location and development environment specific migrations/data assests located in resources/db/dev
With the database files in place all that was needed was a application-dev.properties
file with
info.environment=dev
flyway.locations=classpath:db/migration,classpath:db/dev
This did two things;
- publish the runtime profile to the
\info
endpoint provided by actuator - overrode the default locations flyway examines for migrations and callback files to include both the standard schema migrations as well as any
dev
environment specific files.
One item of note is that in this particular case, the development need was for default data. With this in mind while the data file(s) could be versioned using the standard versioned Flyway naming schema this requires some consideration to make sure that the versions of the dev data assets and schema migrations do not clash. Flyway also supports callbacks which are the perfect solution to this problem (see here). In particular the afterMigrate
hook. Be aware to make your migration idempotent as it will run every time on startup, regardless of the number of migrations executed.
Simple when you know how, but sometimes the documentation isn't that transparent. Hope this helps. A full working example can be found on github here.