Last week I heard that the project I just rolled off was a huge success, both for our UK office (in terms of significant wins and growth), as well as the development team (for the freedom of both process and technology choices). When I started work on the project, I was working with Joe Walnes, and one of the project’s foundation principles was about making technology and process decisions that helped to reduce the build time (and therefore delivery time). I have been on, and heard about, many other projects that end up with several staged builds, leading to the average developer build of around an hour. When I left, we had a sub-minute build (including end to end testing) and a deployable application with a dependency on only Java 5 and Ant (but okay, it’s not a huge system yet).
I’m posting this entry so that somewhere I hope that someone learns something which they apply in optimising their own build (or even sharing their strategies) to make their projects even more agile. What follows is a list of some of the more significant decisions we made, and more importantly, why we made them.
Persistence
I think that for the most part, the only decent Object Relational Mapping (ORM) tool in Java today is Hibernate. For Hibernate versions 3.0 and earlier, you generally wrote XML for configuration which we started with. We thought about using XDoclet, and although it keeps it close to the code, we thought it would be faster to write our own files. After about two days of writing XML for our domain objects, we decided to replace the XML configuration files with Hibernate Annotations (3.1 beta) which not only kept all the code close together, but probably improved Hibernate’s load time since there was no XML parsing. Moving to Annotations, or even just writing XML by hand (with enough smaller-grained functional tests) should be enough to shave several minutes off any build with a decently sized domain model.
Testing
Lessons learned from colleagues indicate that you end up with a staged build after a while, and my personal experience shows that it is much harder to change things with confidence if you only have unit tests, or made slower if you only have integration tests. Starting off, we decided to have three staged builds, one with just unit tests, one with just functional (unit integration) tests and then finally the acceptance (end-to-end) tests.
Our definition for unit tests included anything that was pretty much independent of external resources and could be run on a scale of hundreds a second. The bulk of our functional tests included hibernate integration tests (ensuring that our objects would save to a DB and that retrieval queries would translate into the appropriate SQL). Our acceptance tests were basically web tests that were the driving force for all the code.
A common pattern I have seen for hibernate functional tests is one in which all the objects are saved in a test, and the objects are deleted on tearDown()
or setUp()
to restore the DB to a clean state. The same thing can be achieved if your test fixture starts a transaction on setUp()
, and on tearDown()
rolls it back. To ensure that your tests generate SQL (what you’re trying to test) on persistence make sure they call session.flush()
, or if querying, first empty the domain level caches with a session.clear()
call. This rollback transaction pattern also has great potential for running functional tests on concurrent build machines without running into the issues you would if sharing a common database.
Database
For ease of setup, we originally started off with a local MySQL database (the Hibernate Generate Schema Tool helped us set up our tests databases). Soon after this, we moved to HSQLDB to improve the speed of functional tests even more (with an in memory DB), and then a file based HSQLDB for acceptance tests (since scenario data had to be loaded). There are a few caveats in applying this technique as there can be several differences (case sensitivity, default return order, date format, etc) between a proper production database and one such as HSQLDB, but is still useful nevertheless.
Templating Language
We used WebWork for our web application framework and were originally using the WebWork tags in JSPs to start with. We found that this choice was not ideal for agile acceptance testing because of the precompilation stage each JSP had to go through (either programmatically or on first hit). We had the choice out of Velocity or Freemarker, deciding to go with Freemarker (with no other real reason other than we hadn’t used it before), and our acceptance tests (as well as individual edit-browse cycles) improved.
Source Control
We used Subversion over CVS. We didn’t pick this explicitly because of its speed, but I do think that it has helped to improve local developer builds (as you don’t need to connect to a remote server for Subversion adds or rollbacks).
Future Optimisation Points
Things we were talking about changing before I left included:
- Build Tool – Ant is good, but some of the ant tasks (and Java) can be slow. We were considering using alternatives (Rake anyone?) instead.
- Schema Generation – As your domain model continues to grow, running Hibernate’s Schema Generator probably adds less value. When configured properly HSQLDB dumps all of its contents into a simple SQL file, which we were thinking of using (and updating) as an alternative.
- Acceptance Test Scenario Loading – We use HLoader (a tool for loading Hibernate-bound objects via an XML configuration) to load up acceptance data. We use Hloader because it is less fiddly than dealing with multiple CSV files but gives us the flexibility of more complex structures without writing lots of java code. Like above, we were thinking of using the HSQLDB dumps to replace its contents.
“with no other real reason other than we hadn’t used it before”
That’s an odd reason. I would have gone for Velocity for this same reason.
Having used both JDO and Hibernate, I actually find JDO a little easier to get to grops with. Not only are there some great tools out there (albeit only supplied by the commercial vendors) it has better default mappings, meaning less configuration is required. I have no idea however if there is an OpenSource JDO implementation that is any good – my experiences were based on the very good Kodo JDO.
After using FreeMarker, did you regret your choice? Does it offer anything over and above velocity that you needed/liked?
Jason, I think that we were in that mindset of, if everything else is the same, try new things. In fact it was pretty much a unanimous vote if I remember correctly. I think we could always fall back to velocity if needed.
Sam, I haven’t used JDO in Anger yet and hadn’t yet spoken to anyone in person who has. I’m glad to hear JDO is good and would be good to compare alternatives. As for FreeMarker, I don’t regret using it and would be keen to try it again. I found the documentation was pretty good, but sometimes the syntax was a little bit verbose (I did miss WebWork’s stack type approach for iterators without having to declare additional variables). I did like some of the block syntax as it seems to be a little clearer in a normal text editor. I can’t argue that it is any better/worse than velocity because I haven’t seen both used in total extremes.
I would wait with evaluating Rake for building Java projects until there’s some basic Rake-Ant-integration. Currently Rake would have to spawn a new JVM for each Java-specific thing it needs to do. Result: Slooooow. With Rake-Ant integration it could spawn just one JVM and then send it commands. My current solution to the build dilemma is just to try to keep everything as simple as possible. This obviously doesn’t work when you come to a place when things just aren’t that simple anymore. But if you can, try to keep it simple.
Btw, you should have a look at Spring WebFlow. I’ve been using it for a while on my current project. For certain type of apps it’s incredibly good. (For others, not so good.)