Obviously it's a lot more work to get started with this stack than RoR, but that can be vastly improved with a good prototype project. There are a number of options, ranging from a slightly expanded Maven standard archetype to Appfuse. Usually you can modify an existing project, maybe maintain a company-wide standard application - or even use spring-cookbook :-). In either case, it's not a big deal in a real world situation, since the time spent in this phase is a tiny, tiny part of the whole project. It does matter for newbies, though.
What's more important and interesting is the time and effort needed to make a change to the application. You often see the argument that RoR is more productive because you need to write less code to accomplish the same feat. Case in point: the infamous Hello World application...Ruby first:
puts "Hello, World!"
And the same thing in Java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
Clearly a big advantage for Ruby, wouldn't you say? ;-)
Jokes aside, code-completion compensates for more verbose syntax IMHO. Actually writing code isn't really time-consuming, but the number of places where you have to edit code for a particular change in the application to happen, does matter. It matters a lot. Suppose you have a Car in your domain model, and you want to add a color property. In RoR, you add the column "color" to the table "cars" in the database. That's it. There's no updating the ORM mapping files, no adding of a member , getter and setter for color in the Car class, no updating of DTOs or form beans, no extra handling of parameters. Unless you're using view scaffolding you will need to modify the views, however. This is a great boost to productivity, read- and maintainability of the application.
When working with Hibernate/JPA annotations, you get fairly close to this behaviour . Hibernate can auto-update the schema on changes to the Java model, and you only need to set the @Entity annotation on the class level for properties to be persistent by default. And there's no need for maintaining separate DTOs, as when working with EJB 2.x. If you're using the Spring MVC framework, you can even use arbitrary classes as form beans, and the domain model is of course a great candidate for that. So we can honestly say that we have a fairly competitive setup at hand.
A nice thing in Rails is the automatic conversion from id parameter to a related persisted entity, for example the category of a recipie in this particular application. The HTML form for editing a Recipe has a drop-down list where the parameter value is the id of the Category. Using Spring MVC, we need to write a (short) PropertyEditor to load and join the Recipie and the Category.
When it comes to URL mapping and controllers, Spring 2.0 brings a fantastic new addition to the table: ControllerClassNameHandlerMapping. Together with the MultiactionController we can work similarly to Rails: Any request that matches /car/* is handled by a CarController, and specifically by the method that has the same name as the end part of the url - /car/edit is handled by the edit() method, and so on. It works for other controller types as well, so you can have a SimpleFormController named EditCarController that's mapped to /editCar.
The edit-save-reload-cycle in the web server is almost zero in RoR, since Ruby is a interpreted language. Traditionally, this cycle has been very slow in Enterprise Java development, and even if you work extensively with out-of-container testing, you will need to see and test the actual application from time to time. Until we have on-the-fly class reloading in the JVM, we're going to have to reload the application after making changes to Java code. We can ease the pain by using a smart directory structure (as in this application) and a fast, handy servlet container that scans for changes and triggers reloads automatically. Another thing that's new in Spring 2.0 is the support for beans defined in script languages such as BeanShell, Groovy or even Ruby. You could for example write the controllers in Groovy and keep the service layer and everything below in Java. The possibilites are endless, and the big advantage of Spring is that you can gradually migrate to a simpler and more modern architecture while preserving and interacting with legacy components.
Working with a Rich Domain Model and IoC using the new @Configurable technique, is quite nice. It does require a few tricks and redundant configuration though. Passing the -javaagent parameter to the JVM requires setting shell variables or editing the server startup script, which is acceptable but easy to forget.
It's also unfortunate that there's no domain-specific shorthand XML configuration for the AspectJ transaction aspect, like <aop:aspectj-configured/>.
In general, it would be nice if you didn't have to specify what classes should be persisted twice, first with the @Entity annotation and the again the in SessionFactory configuration. Same thing with classes that need to be weaved with the IoC-aspect: first with @Configurable, and again in classpath:META-INF/aop.xml. With no detailed technical insight in the matter, here's how I'd like to write my configuration, using the same recursive package syntax as AspectJ:
<hibernate3:sessionFactory mappedClasses="se.petrix.cookbook.model..*"/>
<aop:aspectj-configured configuredClasses="se.petrix.cookbook.model..*"/>
<aop:aspectj-transactions/>
Better get to work on that patch then :-)
There is a lot of room for configuration improvment everywhere, now that the domain-specific XML framework is in place. My guess is that we'll see a lot of new XSDs during the 2.x series.
Mock testing is a little bit more difficult, but still manageable. If you're wiring a DAO or service layer into the model, you won't have to deal with chained interface calls either, such as sessionFactory.getCurrentSession().getCriteria(clazz).
How to write the CRUD methods is another difficult question. At first thought, the load operation isn't tied to a particular instance of an entity, since we don't have one yet. Therefore it should be static:
public static Recipe load(Long id) { ... }
Recipe r = Recipe.load(1L);
But there are two problems with this implementation: first, you don't want to make the persistence collaborator (the SessionFactory in this application, maybe a DAO layer or a DataSource in others) a static member. So, we have to wrap the static call with an instantiation:
public static Recipe load(Long id) {
return new Recipe().doLoad(id);
}
And secondly, you don't want to implement this method in every persistent domain class, instead you move it to the BasicEntity class. But how do we instantiate the parameterized class in a static context? We want something like this (in BasicEntity):
public static T load(Long id) {
return T.class.newInstance().doLoad(id);
}
Of course, that's not possible (T.class is illegal). I haven't found any way around this.
The choice, as I see it, is between wrapping load and other static methods such as finders in every persistent class, or completely skip static methods and instead choose one of the following styles:
// 1:
Category c = new Category().load(1L);
// 2:
Category c = new Category(1L).load();
// 3:
Category c = new Category(); c.setId(1L); c.load();
// 4: (perform a load operation in the constructor)
Category c = new Category(1L);
If anyone has opinions on this matter, please post a comment. I'm not convinced about the superiority of any of these solutions, but I've chosen the simplest one, number 3, in this application.