10 September 2009

I was really glad to see Udi Dahan's, "Don't Delete. Just Don't." I wholely agree with the main premise - that deletion is rarely, if ever, a valid business state and that care should be taken in dealing with it. He cites various examples, saying, "The real world doesn't cascade." His example is of a marketing department that deletes an item from the catalog. Should all orders containing that item then be unlinked? Should they redo the company's profit and loss statments?

His example's a perfect illustration of a valid case where care needs to be taken both in modeling the case and dealing with it in code. I fear a lot of people have gotten hooked on instant-models or CRUD "frameworks." The idea that you can annotate a few entity classes and suddenly you have a model and business logic all taken care. A lot of this became popular with Ruby on Rails and has only gotten worse in the JEE world. It's now common to expose EntityManagers to the web tier (both the Seam and Spring Web Flow examples, not to mentino the Open Session in View pattern itself, speak to this) and to let web frameworks handle the lifecycle of entities and committing transactions on that EntityManager to persist those entities. We now think of record creation and managment as so mundane that we've automated it and surrounded it in scaffolding.

Unfortuntately, this misses the true value. Objects in a system have state that has dependencies. Just because an entity has a (required) no-args constructor, it doesn't mean that you should not write a constructor with dependencies. A lot of this stems from the "active objects" or "naked objects" pattern, where objects are open books whose state can be mutated on the UI tier directly and those changes propagated directly. We tell ourselves that we can apply Hibernate validations (or validations in any other ORM framework) and that we'll guarantee valid business constraints thus, but it rarely works out that way. Prescribing the valid creation and destruction of an object is critical.

The most insidious part of this abuse is that it rarely ever occurs in the first iterations of a project, only in maintenance. People "forget" how an object is created, or destroyed. They forget details. Consistancy with other parts of the system is forsaken because, after all, how much could there be to it? This happens more often than you'd expect: new people come on to the team and are assigned to rework an existing feature and screen in a system. Institutional knowledge is important but it's not always easily transferred, so safe-guards need to be taken.

Plus, as business evolves, object creation is often only part and parcel with a larger process. If a new customer is added to a database, and nobody does anything with it, did it really get added? Put another way: if a new customer gets added to a database, there's an implied lifecycle that customer must follow. Perhaps a trial period window and then a follow up email? Perhaps a monthly subscriptio fee? Who knows, but the act of instantiating a Customer object and then issuing entityManager.persist( customer ) will rarely, if ever, be enough.

Indeed, I've seen many projects build their models using only a GenericDao<T> variant. This is dangerous. Transitions from one state to another for an entity can often cost money.

Do things the old fashioned way, building services that handle the business operations, not merely Create, Read,Update and Delete objects. Using Dao's in a view tier is almost as ghastly a sin as making JDBC calls from the view, and encourages the same kind of copy-n-paste code reuse we prided ourselves on having moved away from.

While the services approach isn't exactly code-generation friendly, it can pay off in the long run. As soon as you decide to use your services in a phsically separated tier, you'll be glad. It's easier to build services that express every dependency as a method parameter. There are several reasons why this works out. While web service support is getting better, object marshalling is still the number one headache. Most toolkits make interoping with primitives a brease, though. It may seem like your losing ground by not taking advantage of the POJO you have floating around in the view tier, but in point of fact it's rarely that big a burden to unmarshal the relevant fields and use those instead. On the services side, reloading the relevant record is painless because the entity's usually cached and thus refetching it is cheap.

Take for example the idea of a shopping cart which surfaces methods for adding line items to orders, orders to shopping carts and shopping carts to customers. A clean version of this might look like:

 
 public interface ShoppingCartService { 
     ShoppingCart createShoppingCartForCustomer( long customerId, 
                                                 Date 
whenShoppingSessionStarted ) ; 
     Order createOrderForShoppingCart( long shoppingCart ) ; 
     LineItem createLineItemForOrder( long orderId, long productId, 
int quantity) ; 
     void removeLineItemFromOrder( long orderId, long lineItem ) ; 
     void startOrderFulfillment( long orderId ) ; 
     /*  other methods for loading and removing the various 
         objects, as appropriate 
     */ 
  } 

In these methods, you can do sanity checks, care for business cases, handle the state of the various objects and not worry about having a transactional resource like an EntityManager or Hibernate Session open in the client thread. In this example, you might imagine the Order won't officially be fulfilled until it's paid for, at which point it'll move to the PAID state. Simialarly, adding a LineItem might require a check to an inventory, decrementing how many of those Products are in stock. The same is true of the inverse operation: removeLineItemFromOrder should appropriately restore the inventory count and update the totalPrice field on the Order.

This requires one interface method declaration's work more than if you had simply obtained the EntityManager from your Seam backing bean and executed the logic there, except now the logic's codified and reusable.