Monday, December 05, 2005

Software Productivity in the Java World

Recently, I have been playing around with the JBoss EJB3 implementation, and I have to say that I have been impressed with where the specification is heading. I have long been following the J2EE, and now the Java EE specifications. In the beginning, EJB was better than what I had been doing with CORBA, and seemed to have great promise. Of course, once we started to develop large scale applications, the weaknesses of the component model started to become evident.

The component model, led to many anti-patterns, in that in order to have any reasonable application, you needed to implement value or transfer objects, so you could pass results up to the web tier of an application. You needed business delegates, and value list handlers to make business logic easier to implement, and result sets from queries reasonable for the web tier to deal with. These are only some of the patterns that became common in J2EE development. With all of these, things just become more complicated for developers, and less productive than was anticipated by any of the original specifications developers anticipated. Also, the deployment descriptor approach, which on the surface seemed like it would make things easier for developers, really just made it harder, because it wasn't just coding Java classes, or components anymore, you had to also describe the behavior you wanted external to the code. This gave rise to XDoclet, which anyone who has done J2EE development, knows certainly that it helped alleviate some of the complexity, but introduced its own complexities. That brings us to the new EJB3 specification, and the JBoss implementation.

The first thing you will notice is that the entire component model (at least for the most part) is gone, and replaced with just Plain Old Java Objects (POJO). This is a great step forward, as it removes the complexity of home interfaces, remote and local interfaces, and you don't need to have separate transfer objects and the like, because your POJO can just implement Serializable, and it can be moved between any of the tiers of your application. Of course, the magic that makes all this happen, is the use of annotations, and having defaults that are reasonable, so you don't have to specify things that you shouldn't have to. In fact, they went to a development model by convention versus doing it by declaring everything through the deployment descriptors. For example, this is what an entity bean looks like:

package services.entities;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Order implements Serializable {

private long orderId;
private long customerId;
private Address shippingAddress;
private BigDecimal totalOrderAmount;
private List orderLines;

public Order() {

}

public Order(long customerId
, Address shippingAddress
, BigDecimal totalOrderAmount) {

this.customerId = customerId;
this.shippingAddress = shippingAddress;
this.totalOrderAmount = totalOrderAmount;

}

@Id(generate = GeneratorType.AUTO)
public long getOrderId () {

return orderId;

}

public void setOrderId(long orderId) {

this.orderId = orderId;

}

public long getCustomerId() {

return customerId;

}

public void setCustomerId(long customerId) {

this.customerId = customerId;

}

public String getShippingAddressLine1() {

return shippingAddress.getAddressLine1();

}

public void setShippingAddressLine1(String shippingAddressLine1) {

shippingAddress.setAddressLine1(shippingAddressLine1);

}

public String getShippingAddressLine2() {

return shippingAddress.getAddressLine2();

}

public void setShippingAddressLine2(String shippingAddressLine2) {

shippingAddress.setAddressLine2(shippingAddressLine2);

}

public String getShippingCity() {

return shippingAddress.getCity();

}

public void setShippingCity(String shippingCity) {

shippingAddress.setCity(shippingCity);

}

public String getShippingState() {

return shippingAddress.getState();

}

public void setShippingState(String shippingState) {

shippingAddress.setState(shippingState);

}

public int getShippingZipCode() {

return shippingAddress.getZipCode();

}

public void setShippingZipCode(int shippingZipCode) {

shippingAddress.setZipCode(shippingZipCode);

}

public int getShippingZipCodePlusFour() {

return shippingAddress.getZipCodePlusFour();

}

public void setShippingZipCodePlusFour(int shippingZipCodePlusFour) {

shippingAddress.setZipCodePlusFour(shippingZipCodePlusFour);

}

public BigDecimal getTotalOrderAmount() {

return totalOrderAmount;

}

public void setTotalOrderAmount(BigDecimal totalOrderAmount) {

this.totalOrderAmount = totalOrderAmount;

}

@OneToMany(mappedBy="order", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
public List getOrderLines() {

return orderLines;

}

public void setOrderLines(List orderLines) {

this.orderLines = orderLines;

}

public boolean equals(Object o) {

if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass()) {
return false;
}

final Order order = (Order) o;

if (!(orderId == order.orderId)) {
return false;
}

if (!(customerId == order.customerId)) {
return false;
}

if (!shippingAddress.equals(order.shippingAddress)) {
return false;
}

if (!totalOrderAmount.equals(order.totalOrderAmount)) {
return false;
}

if (!orderLines.equals(order.orderLines)) {
return false;
}

return true;

}

public int hashCode() {

int hashValue;

hashValue = Long.toString(orderId).hashCode()
+ Long.toString(customerId).hashCode()
+ shippingAddress.hashCode()
+ totalOrderAmount.hashCode()
+ orderLines.hashCode();

return hashValue;

}

}

Let me point out some interesting things. This is just a plain java object, nothing special. The thing that makes it an Entity bean, is the fact that it has the @Entity annotation. That is all there is to making it an entity bean. There are really only three other things in this example that are meaningful. First is the primary key of the object, for persistence purposes it is identified by the @Id annotation. It specifies that the orderId is the primary key, and that it is autogenerated. In this case, I am using a MySQL database underneath, and using an autoincrement column type. Of course, there are annotations that work across every relational database that matters, such as DB2 identity column types, Oracle sequences, etc. You can even customize it to create your own autogenerated types, so it is completely extensible in this regard. The second thing to notice is that I implement Serializable, and implemented the hashcode and equals methods. Just standard Java coding, nothing special. The final thing to notice, is the annotation that says @OneToMany. This annotation specifies that this entity has an association (actually it is an aggregate) with another entity, and what attribute that association is based on. It also specifies the fetch behavior that you want for the collection. Once again, this is very simple, and it is in the code, no special deployment descriptor required!

Everything else, the table that it maps to, the column mapping, etc., are all done by convention. If you simply make the table and object names the same you don't have to specify anything. If the column and attribute names are the same, you don't have to specify mapping for the columns either. Of course, you can use an @Table annotation, and an @Column annotation for specify these things, but why do that, just follow the convention and save yourself all the trouble.

In the old EJB model, everyone eschewed the use of entity beans, because you could have a lot of trouble creating a scalable application using them. In this new model, you have a full featured query language, and very simple persistence. You have a persistence context that you can specify through an annotation (@PersistenceContext), and an EntityManager class. You can just call the EntityManager persist method passing the entity object, and it will get saved to the database. You can create as fine a grained, or course a grained model as you want, and still be able to create a scalable application. What a difference!

Of course, I'm just scratching the surface, with the entity bean example, but you start getting the picture. One of the other really nice things about EJB3, is that by specification, it is embeddable in any application, not just ones running in an application server. This has opened up the capability to make all sorts of applications, and it makes the capability to unit test much easier. In fact, I recently figured out how to use the JBoss embeddable version of EJB3 and JUnit from within Eclipse to create unit test suites and unit tests that test the POJO's of EJB3. This gives another boost to developer productivity, especially if you follow a test driven development paradigm.

This is a great step forward. If you have any experience with this, let me know what you think.

No comments: