Статьи

Весенние Профили или Maven Профили?

Развертывание в разных средах требует настройки,  например,  URL базы данных должны быть установлены в каждой выделенной среде. В большинстве — если не во всех Java-приложениях это достигается с помощью файла .properties, загружаемого через Properties класс с соответствующим именем  . Во время разработки нет причин не использовать одну и ту же систему конфигурации,  например,  использовать встроенную базу данных h2 вместо рабочей.

К сожалению, приложения Jave EE, как правило, выходят за рамки этого использования, поскольку эффективная практика в развернутых средах ( т. Е. Во  всех средах, за исключением локальной машины разработчика) заключается в использовании источника данных JNDI вместо локального соединения. Даже Tomcat и Jetty, которые реализуют лишь часть веб-профиля Java EE, предоставляют эту изящную и полезную функцию.

В качестве примера давайте возьмем среду Spring. В этом случае необходимо определить два фрагмента конфигурации источника данных:

  • Для развернутой среды, которая определяет поиск местоположения JNDI
  • Для локальной разработки (и тестирования), которая настраивает пул соединений вокруг прямого соединения с базой данных

Файл простых свойств не может управлять этим типом переключателя, нужно использовать систему сборки. Бесстыдная самореклама : подробное объяснение этой настройки для целей интеграционного тестирования можно найти в моей книге «  Интеграционное тестирование из окопов» .

С помощью системы сборки Maven изменение конфигурации достигается с помощью так называемых профилей  во время сборки . Грубо говоря, профиль Maven — это часть POM, которая может быть включена (или нет). Например, следующий фрагмент профиля заменяет стандартный каталог ресурсов Maven выделенным.

<profiles>
    <profile>
      <id>dev</id>
      <build>
        <resources>
          <resource>
            <directory>profile/dev</directory>
            <includes>
              <include>**/*</include>
            </includes>
          </resource>
        </resources>
      </build>
    </profile>
</profiles>

Активировать один или несколько профилей так же просто, как использовать  -P переключатель с их идентификатором в командной строке при вызове Maven. Следующая команда активирует  dev профиль (при условии, что он установлен в POM):

mvn package -Pdev

Теперь давайте добавим простое требование: поскольку я довольно ленив, я хочу приложить минимальные усилия, чтобы упаковать приложение вместе с его окончательной производственной конфигурацией выпуска. Это приводит к созданию рабочей конфигурации,  то есть  фрагмента JNDI,  по умолчанию , и к явному использованию фрагмента разработки при необходимости. Опытные пользователи Maven знают, как это реализовать: создайте производственный профиль и настройте его по умолчанию.

<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.