Saturday, April 30, 2011

Spring 3.1 Bean Definition Profiles - Use Case

As I am diving deeper into VMware's Cloud Foundry, I decided to make the DevNexus conference web-application fit for Cloud Foundry. However, instead of creating an explicit version of the application for Cloud Foundry, my goal is to have one War file that can be deployed without modifications to either CloudFoundry or a stand-alone servlet container.

Here one new Spring 3.1 feature comes in handy: Bean definition profiles. Bean definition profiles allow you to conditionally load Spring beans. There are some great resources already out there on how to use Bean definition profile, and thus I won't go into the details here:
How am I using Bean Definition Profiles for the DevNexus web-application? 

Personally, I like to develop web applications that can run in a wide variety of environments with as little configuration as necessary. For my use-case, I want to create a single war file that can be deployed to the cloud,  but which can also be deployed into your locally running Tomcat, Jetty, etc.

Furthermore, I believe that bean definition profiles can also be very useful for demo/integration testing purposes. For web applications, I like the concept of "application home directories". They contain configuration files (e.g. DB connectivity parameters), data files such as for Hibernate Search etc.) and the web application will reference the home directory either through a system property, environment variable or looks in a default directory in your user directory. But what if the home directory does not exist?

If the home directory does not exist, then the application runs in demo mode (or embedded mode), which means, the application use an embedded database for persistence and to also loads a set of seed+demo data at application startup-up.

Summarizing, I can think of the following Web application "modes" aka profiles:
  • Embedded mode (Integration testing and Demo mode)
  • Cloud Foundry mode (e.g. reference the MySql service, don't use file-system properties)
  • Stand-alone deployment mode (Uses home directory)
Thus, I can create a dedicated Bean definition profile in my Spring application context for each mode. Here is an example I have:

 <beans profile="default">
        <bean id="propertyConfigurer"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="searchSystemEnvironment" value="true" />
            <property name="locations">
                <list>
                    <value>file:${TING_HOME}/ting.properties</value>
                </list>
            </property>
        </bean>
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
            destroy-method="close">
            <property name="driverClassName" value="${database.jdbc.driverClassName}" />
            <property name="url" value="${database.jdbc.url}" />
            <property name="username" value="${database.jdbc.username}" />
            <property name="password" value="${database.jdbc.password}" />
            <property name="maxActive" value="100" />
            <property name="maxIdle" value="30" />
            <property name="maxWait" value="1000" />
            <property name="poolPreparedStatements" value="true" />
            <property name="defaultAutoCommit" value="true" />
        </bean>
        <bean id="entityManagerFactory"
            class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
            <property name="persistenceUnitName" value="base" />
            <property name="jpaProperties">
                <props>
                    <prop key="hibernate.dialect">${database.hibernate.dialect}</prop>
                    <prop key="hibernate.query.substitutions">true '1', false '0'</prop>
                    <prop key="hibernate.generate_statistics">true</prop>
                    <prop key="hibernate.cache.use_second_level_cache">true</prop>
                    <prop key="hibernate.cache.use_query_cache">true</prop>
                    <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</prop>
                    <prop key="hibernate.show_sql">${database.hibernate.show_sql}</prop>
                    <prop key="hibernate.format_sql">true</prop>
                </props>
            </property>
        </bean>
    </beans>
    <beans profile="cloud">
        <cloud:data-source id="devnexus-db"/>
        <bean id="entityManagerFactory"
            class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="devnexus-db" />
            <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
            <property name="persistenceUnitName" value="base" />
            <property name="jpaProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                    <prop key="hibernate.query.substitutions">true '1', false '0'</prop>
                    <prop key="hibernate.generate_statistics">true</prop>
                    <prop key="hibernate.cache.use_second_level_cache">true</prop>
                    <prop key="hibernate.cache.use_query_cache">true</prop>
                    <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</prop>
                    <prop key="hibernate.show_sql">false</prop>
                    <prop key="hibernate.format_sql">true</prop>
                    <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
                </props>
            </property>
        </bean>
    </beans>
    <beans profile="embedded">
        <jdbc:embedded-database type="H2" id="dataSource"/>
        <bean id="entityManagerFactory"
            class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
            <property name="persistenceUnitName" value="base" />
            <property name="jpaProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
                    <prop key="hibernate.query.substitutions">true '1', false '0'</prop>
                    <prop key="hibernate.generate_statistics">true</prop>
                    <prop key="hibernate.cache.use_second_level_cache">true</prop>
                    <prop key="hibernate.cache.use_query_cache">true</prop>
                    <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</prop>
                    <prop key="hibernate.show_sql">false</prop>
                    <prop key="hibernate.format_sql">false</prop>
                </props>
            </property>
        </bean>
    </beans>

What about integration testing? 

As of writing this blog posting the latest Spring 3.1 release is: 3.1.0.M1 and there is no explicit support for selecting profiles in your test declaratively. There is, though, the following new feature request Jira:

https://jira.springsource.org/browse/SPR-7960 - "TestContext framework should support declarative configuration of bean definition profiles"

This feature is scheduled for Spring 3.1 M2, but in the meantime, I use a static block which does the trick:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
        locations={
          "classpath:spring/applicationContext-core-data.xml",
          "classpath:spring/mainApplicationContext.xml"
                 })
public abstract class BaseDaoIntegrationTest {
 
static {
  System.setProperty("spring.profiles.active", "embedded");
 }
 
    protected @PersistenceContext(unitName="base") EntityManager entityManager;

}

Over the next few days I hope to deploy an updated DevNexus application to Cloud Foundry, so stay tuned for updates.

3 comments:

James Ward said...

Hey Gunnar,

Not sure if it helps, but I am able to set the profile to use via Maven for my integration tests:


org.apache.maven.plugins
maven-surefire-plugin
2.7.1


dev




-James

James Ward said...

(Shoot. XML denied. Let me try again)

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.1</version>
<configuration>
<systemPropertyVariables>
<spring.profiles.default>dev</spring.profiles.default>
</systemPropertyVariables>
</configuration>
</plugin>

benze said...

Thanks for the tips; I'm using some of your ideas. However, given your configuration, how do you automatically detect which configuration to use?