Thursday, April 24, 2008

Mock Your Spring Config for Fun and Profit

eeeeeeye of the tigeeeeeeer!!!

"Nemesis: An opponent that cannot be beaten or overcome." (Answers.com)

I used to be quite proud of our spring config structure, which is the result of some trial-and-error game while trying to achieve the perfect pluggable system. I would much prefer avoiding the typical LDAP / Oracle / BankingHost-with-potatoes combo just to debug the web interface while my laptop screams wildly. Besides, I'm green: I love saving power by keeping the system startup under 15 seconds.

Note that I said "green", not "impatient". Green. Ecological, say.

But last month I met The Adversary: twelve systems to integrate, a different environment for test, production and disaster recovery, four different web applications that communicate using web services, up to four different databases... Our spring mojo was not cutting it, so we had to take it to the next level.

What follows are all the alternatives that we tried, what worked and what didn't:

PropertyPlaceHolderConfigurer



The bread and butter of application configuration, this class gets into your spring file and you start injecting values anywhere using the classic ant-like syntax we all know and love:

config.properties:

mail.user=foo
mail.password=bar


spring-config.xml:


<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:config.properties"/>
</bean>

<bean class="com.acme.SomeClass" p:username="${mail.user}" p:password="${mail.password}"/>


PropertyPlaceHolderConfigurer includes a systemPropertiesMode attribute to override configuration using the command line. For example, this example could be overriden adding -Dmail.user=baz to the server startup script.

Spring imports



This is an old habit of mine, a "main index file" that imports everything else. It is a bit cleaner than specifying every file in the web.xml descriptor:


<?xml version="1.0" encoding="UTF-8"?>
<beans>

<import resource="spring-properties.xml"/>
<import resource="spring-mail.xml"/>
<import resource="spring-web.xml"/>
<import resource="spring-persistence.xml"/>
<import resource="spring-security.xml"/>
<import resource="spring-scheduler.xml"/>

</beans>


Say I want to work at home, and for the time being I would prefer to replace "spring-mail.xml" with "mock-mail.xml". Well, PropertyPlaceHolderConfigurer cannot be used for this because substitution is applied after the imports are executed, instead of before.

Our first idea was to process this file using ant to replace placeholders like ${spring.mail.file} with the actual file location. It works, but launching ant after every change in the environment is time-consuming, anti-agile, and plain ugly. You also end up manually inspecting files to see which mocks are being applied, which sucks.

Spring JavaConfig



Spring JavaConfig is a promising little project still in its alpha stage. It aims at providing a more flexible way to configure spring using your favorite JVM language (java, groovy, etc). Definitely a good idea.

At first sight it may cut it:


@Configuration
public abstract class MockConfiguration {

/** set to true to activate mock */
private boolean mockMail;

@Bean
public MailInterface mail() {
return mockMail? mockMail() : realMail();
}

// The main xml file should define both real and mock beans,
// we just proxy them
@ExternalBean
public abstract MailInterface realMail();

@ExternalBean
public abstract MailInterface mockMail();

}


This class is like advanced maths courses at my University: it is correct, works, is kinda cute, and is totally useless.

This solution involves too much code per mocked system. With about a dozen external systems, MockConfiguration can get quite huge, and your (integration) tests can get quite complicated since they will include parts of the global system, where no "mail" bean will be defined.

In my experience, Spring JavaConfig is a great solution looking for an appropriate problem, which mine is not. We moved on.

Placeholders in ApplicationContext



I found this while scanning some spring sources. Suppose your web.xml file includes several spring files:


<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/myconfig.xml,classpath:spring/otherconfig.xml</param-value>
</context-param>


Well, according to AbstractRefreshableConfigApplicationContext.resolvePath, you could also have used placeholders:


<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/${SPRING_MAIL},classpath:spring/${SPRING_PERSISTENCE}</param-value>
</context-param>


Then define them as system properties (using -D) or environment variables:


export SPRING_MAIL=mock-mail.xml
export SPRING_PERSISTENCE=mock-mail.xml

- or -

java -DSPRING_PERSISTENCE=mock-persistence.xml -DSPRING_MAIL=mock-mail.xml ...


The only problem with this is that I don't like solutions that involve the system administrators too much: the war file is not fully configured by itself, which could be a concern, and involves an amount of work proportional to the number of servers to deploy. It is also a bit hard to automatize.

Damn. This one has been long, and I hate long posts. Well, here it is in the hope that someone else finds it useful, or decides to contribute with his/her own experience. Back to work!

UPDATE: You will see a comment by Mark Fisher that explains how to use PropertyPlaceHolderConfigurer with import statements, which renders the last part of this post useless so I decided to remove it (again: I hate long posts).
UPDATE(2): For Yet Another Approach at spring config, check out this post by Robert Mark Bram.

9 comments:

  1. Ignacio,

    Very interesting post. I just wanted to point out that it *is* possible to use the property-placeholder for your imports. Just be sure to include the PropertyPlaceholderConfigurer in the same file that defines the "import" elements.

    For example (I'm using the 2.5 'context' namespace here), this works fine:

    <context:property-placeholder/>
    <import resource="${env}.xml"/>

    Cheers,
    Mark

    ReplyDelete
  2. Hey Mark,

    Thanks! I thought I had tested it, but obviously I didn't. Your way is far easier.

    Thanks for sharing :)

    ReplyDelete
  3. Gleb StarodubtsevApril 27, 2008 at 4:03 PM

    Hi,

    >>This is an old habit of mine, a "main index file" that imports everything else. It is a bit cleaner than specifying every file in the web.xml descriptor

    You can also use *(asterisk) for the same purpose:

    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/spring*.xml</param-value>
    </context-param>

    ReplyDelete
  4. Gleb, thanks for the tip. I didn't try wildcards! After some extra investigation, it seems that you can also use URLs :)

    Mark, I found some extra time to peek at spring 2.5, and it seems that PropertyPlaceHolderConfigurer isn't considered before import resolution; System properties are, so you could use placeholders and replace them using system properties and environment variables, but not with properties files.

    ReplyDelete
  5. Great Post Ignacio, and well done on solving the problem - what a complicated system you have. :)

    Did it all work out? Did you have any other difficulties?

    Rob
    :)

    P.S. You spelt my name wrong though - Robert Mark Bram. :-p

    ReplyDelete
  6. Ooops. My bad. Fixed.

    Well, I can tell you that the system works and is surely non-optimal. For security reasons we had to encript some entries using the EncriptablePlaceHolder included in Jasypt, but that was all.

    If I had to redo the thing from scratch, I would certainly try your solution too. It seems clean.

    ReplyDelete
  7. According to Marcs Post about import and placeholders:

    I have to use Spring 2.08 and it seems that

    &lth;import resource="${env}.xml"/>does not work.

    PropertyPlaceholder ist defined correctly - as in other beans the replacement works fine, but not in the import-element.

    Any ideas? Or does this work only correctly in Spring 2.5?

    ReplyDelete
  8. As told in a previous comment, AFAIK PropertyPlaceHolders are not resolved before imports (I checked out the sources of 2.5). You will have to use System properties or environment variables instead.

    ReplyDelete
  9. Thank you!
    You refer to the system variable only in the web.xml in context-parameter and not in the app-context in the import statement?
    At least a refrence in the import-tag does not work in my procect, even when I'm referring to a system variable.

    ReplyDelete

Something on your mind?

Note: Only a member of this blog may post a comment.