söndag 8 mars 2009

Making Java EE less of a pain

Java EE has improved a lot since the days of EJB 2.1. It's still a pain though. I'd say that application servers are responsible for 75% or so of the pain - I don't like application servers, in case that's not clear - but the APIs and required patterns are not to be forgotten.

Me and my colleague have spent a lot of time discussing/fighting about Java EE application architecture. One camp likes application servers and prefer passing domain objects to controllers and to the presentation. Another camp don't like application servers and prefer strict transactional boundaries through which no domain objects are permitted to leak. I don't like application servers and I think that the business domain should stay in the business layer (guess how I feel about JBoss Seam). A consequence of my position is that I inevitably must like DTOs over passing domain objects to controller/presentation layer. Well... not so much. It's a pain to compose DTOs and it makes programming boring.

There are automated tools out there for generating DTOs from domain objects, but in my experience they solve the problem by introducing another problem, namely the need for XML configuration (why is XML always the solution?). So, I figured I'd start an open source project where different Java EE strategies can be tested and discussed. It's aptly named Bumblebee (it's freakin' hopeless to find an available name these days) and currently implements DTO assembly using annotations only.

Ok, lets start with a simple example. Most business domains define the concept of a user. A user generally has some information associated with him/her, and for the sake of argument I'll separate these into two entities, like this:

@Entity
public class User {
public Long getId() { ... }
public String getUsername() { ... }
public UserInfo getUserInfo() { ... }
}

@Entity
public class UserInfo {
public String getFirstName() { ... }
public String getLastName() { ... }
}

If we don't want the domain objects to be accessible from the presentation/controller/session layers, we'll need to define data transfer objects that transfer raw data out of the business layer. Let's say we want to encapsulate username, first name and last name in a DTO. Our resulting business method would typically look like this:

@Stateless
public class UserServiceBean implements UserService {
public UserDetails getUserDetails(String username) {
User user
= userDAO.findByUsername(username);
UserDetails userDetails
= new UserDetails();

userDetails.setUsername(user.getUsername());
userDetails.setFirstName(user.getUserInfo().getFirstName());
userDetails.setLastName(user.getUserInfo().getLastName());

return userDetails;
}
}

At some point, developers will become bored and write a converter/assembler for this, but the code is still there somewhere in the system. This is precisely the code I want to avoid, because it has nothing to do with the business logic, but is rather used to cope with technical limitations. So the goal in the Bumblebee DTO assembler is to provide automatic assembly of transfer objects while requireing as little information as possible from the developer.

To define and assemble a DTO, you need to define a DataObject like such:

@DataObject
public interface UserDetails {

@Value
public String getUsername();

@Value(
"userDetails.firstName")
public String getFirstName();

@Value(
"userDetails.lastName")
public String getLastName();

}

When assembling our DTO in the business layer, we'll call upon Bumblebee to do the work:

import static com.googlecode.bumblebee.dto.Bumblebee.*;

@Stateless
// (or @Service or whatever)
public class UserServiceBean implements UserService {

public UserDetails getUserDetails(String username) {
User user
= userDAO.findByUsername(username);
return assemble(UserDetails.class).from(user);
}
}

...and that's it. The framework will generate a concrete implementation class that extracts the data from the domain object using the provided expressions in the @Value annotations (DTO EL, currently only supports property paths).

That's my two cents. You'll find the project at http://code.google.com/p/java-bumblebee