Deploying on different environments requires configuration, e.g. database URL(s) must be set on each dedicated environment.
In most - if not all Java applications, this is achieved through a .properties file, loaded through the appropriately-named Properties
class.
During development, there’s no reason not to use the same configuration system, e.g. to use an embedded h2 database instead of the production one.
Unfortunately, Jave EE applications generally fall outside this usage, as the good practice on deployed environments (i.e. all environments save the local developer machine) is to use a JNDI datasource instead of a local connection. Even Tomcat and Jetty - which implement only a fraction of the Java EE Web Profile, provide this nifty and useful feature.
As an example, let’s take the Spring framework. In this case, two datasource configuration fragments have to be defined:
- For deployed environment, one that specifies the JNDI location lookup
- For local development (and test), one that configures a connection pool around a direct database connection
Simple properties file cannot manage this kind of switch, one has to use the build system. Shameless self-promotion: a detailed explanation of this setup for integration testing purposes can be found in my book, Integration Testing from the Trenches.
With the Maven build system, change between configuration is achieved through so-called profiles at build time. Roughly, a Maven profile is a portion of a POM that’s can be enabled (or not). For example, the following profile snippet replaces Maven’s standard resource directory with a dedicated one.
<profiles>
<profile>
<id>dev</id>
<build>
<resources>
<resource>
<directory>profile/dev</directory>
<includes>
<include>*/*</include>
</includes>
</resource>
</resources>
</build>
</profile>
</profiles>
Activating a single or different profiles is as easy as using the -P
switch with their id on the command-line when invoking Maven.
The following command will activate the dev
profile (provided it is set in the POM):
mvn package -Pdev
Now, let’s add a simple requirement: as I’m quite lazy, I want to exert the minimum effort possible to package the application along with its final production release configuration. This translates into making the production configuration, i.e. the JNDI fragment, the default one, and using the development fragment explicitly when necessary. Seasoned Maven users know how to implement that: create a production profile and configure it to be the default.
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
...
</profile>
Icing on the cake, profiles can even be set in Maven settings.xml files. Seems to good to be true? Well, very seasoned Maven users know that as soon as a single profile is explicitly activated, the default profile is de-activated. Previous experiences have taught me that because profiles are so easy to implement, they are used (and overused) so that the default one gets easily lost in the process. For example, in one such job, a profile was used on the Continuous Integration server to set some properties for the release in a dedicated setting files. In order to keep the right configuration, one has to a) know about the sneaky profile b) know it will break the default profile c) explicitly set the not-default-anymore profile.
Additional details about the dangers of Maven profiles for building artifacts can be found in this article.
Another drawback of this global approach is the tendency for over-fragmentation of the configuration files. I prefer to have coarse-grained configuration files, with each dedicated to a layer or a use-case. For example, I’d like to declare at least the datasource, the transaction manager and the entity manager in the same file with possibly the different repositories.
Come Spring profiles.
As opposed to Maven profiles, Spring profiles are activated at runtime.
I’m not sure whether this is a good or a bad thing, but the implementation makes it possible for real default configurations, with the help of @Conditional
annotations (see my previous article for more details).
That way, the wrapper-around-the-connection bean gets created when the dev
profile is activated, and when not, the JNDI lookup bean.
This kind of configuration is implemented in the following snippet:
@Configuration
public class MyConfiguration {
@Bean
@Profile("dev")
public DataSource dataSource() throws Exception {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:file:~/conditional");
dataSource.setUsername("sa");
return dataSource;
}
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource fakeDataSource() {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource("java:comp/env/jdbc/conditional");
}
}
In this context, profiles are just a way to activate specific beans, the real magic is achieved through the different @Conditional
annotations.
Note: it is advised to create a dedicated annotation to avoid String typos, to be more refactoring friendly and improve search capabilities on the code.
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@Profile("dev")
public @interface Development {}
Now, this approach has some drawbacks as well. The most obvious problem is that the final archive will contain extra libraries, those that are use exclusively for development. This is readily apparent when one uses Spring Boot. One of such extra library is the h2 database, a whooping 1.7 Mb jar file. There are a two main counterarguments to this:
- First, if you’re concerned about a couple of additional Mb, then your main issue is probably not on the software side, but on the disk management side. Perhaps a virtual layer such as VMWare or Xen could help?
- Then, if the need be, you can still configure the build system to streamline the produced artifact.
The second drawback of Spring profiles is that along with extra libraries, the development configuration will be packaged into the final artifact as well. To be honest, when I first stumbled this approach, this was a no-go. Then, as usual, I thought more and more about it, and came to the following conclusion: there’s nothing wrong with that. Packaging the development configuration has no consequence whatsoever, whether it is set through XML or JavaConfig. Think about this: once an archive has been created, it is considered sealed, even when the application server explodes it for deployment purposes. It is considered very bad practice to do something on the exploded archive in all cases. So what would be the reason not to package the development configuration along? The only reason I can think of is: to be clean, from a theoretical point of view. Me being a pragmatist, I think the advantages of using Spring profiles is far greater than this drawback.
In my current project, I created a single configuration fragment with all beans that are dependent on the environment, the datasource and the Spring Security authentication provider. For the latter, the production configuration uses an internal LDAP, so that the development bean provides an in-memory provider.
So on one hand, we’ve got Maven profiles, which have definite issues but which we are familiar with, and on the other hand, we’ve got Spring profiles which are brand new, hurt our natural inclination but gets the job done. I’d suggest to give them a try: I did and am so far happy with them.