Domain-driven design with Java EE 6

An object-oriented architecture for interactive applications

1 2 3 Page 2
Page 2 of 3
public class LoadTest {

    @Test(expected=IllegalStateException.class)
    public void empty(){
        Load build = new Load.Builder().build();
    }

    @Test
    public void mixedLoad(){
        Load load = new Load.Builder().
                withStandardItem(5).
                withLightweightItem(1).
                withBulkyItem(1).
                build();
        int actual = load.getShippingCosts();
        int expected = (5*5) + (5-1) + (1*5+5);
        assertEquals(expected,actual);
    }
    @Test
    public void heaviest(){
        Load load = new Load.Builder().
                withStandardItem(5).
                withLightweightItem(1).
                withBulkyItem(1).
                build();
        OrderItem heaviest = load.heaviest();
        assertNotNull(heaviest);
        assertEquals(5,heaviest.getWeight());
        assertTrue(heaviest instanceof StandardItem);
    }
//...
}

PDOs are nontrivial by definition; they are designed to reflect concepts from the target domain. Unit tests of PDOs are essential for the overall quality of the system. In addition, you are forced to use the PDOs via the public API, which forces you to think outside the box. Unit testing turns out to be a good usability check for a PDO's interface "fluency." Any inconveniences are directly exposed to you during the tests. And good PDOs are easy to test -- a form of quality assurance.

With minimal effort, JPA persistence can be used for testing. You need to create only the EntityManager, using the Persistence class and the EntityManagerFactory, as shown in Listing 8.

Listing 8. PDO tests with local EntityManager

public class LoadTestWithPersistence {

    private EntityManager em;
    private EntityTransaction et;

    @Before
    public void setUp() throws Exception {
        this.em = Persistence.createEntityManagerFactory("test").
createEntityManager();
        this.et = this.em.getTransaction();
    }

    @Test
    public void mixedLoad(){
        Load load = new Load.Builder().
                withStandardItem(5).
                withLightweightItem(1).
                withBulkyItem(1).
                build();
        int actual = load.getShippingCosts();
        int expected = (5*5) + (5-1) + (1*5+5);
        assertEquals(expected,actual);
        this.et.begin();
        this.em.persist(load);
        this.et.commit();
        this.em.clear();
        Long id = load.getId();
        this.et.begin();
        Load found = this.em.find(Load.class, id);
        this.et.commit();
        assertEquals(expected,found.getShippingCosts());
        assertEquals(load.getNumberOfOrderItems(),found.getNumberOfOrderItems());
        //...
    }
//...
}

Local unit tests could even take on characteristics of integration tests. Rudimentary tests with an EntityManager are at least useful for finding object/relational-mapping problems. For more-complex applications, unit tests without persistence become increasingly bloated, and creating the test fixture can get overly complex. In such cases a local test database can make it much easier to populate the test data once and reuse it for several test runs.

Facades or gateways?

PDOs are passive artifacts. It's impossible to access them directly without an execution context. The next problem, then, is the stateless nature of most Java EE applications. After a method invocation of a transaction boundary (such as a service or service facade) all JPA entities (PDOs) become detached. The client loses its state. This forces you to transport the whole context back and forth between the client and the server, which leads to the following problems:

  • Heavily interconnected PDOs become hard to merge. Even for fine-grained changes, the whole graph of objects must be transported back to the server. It is not always possible to merge the graph automatically and even consistently.
  • Because the transport of the context becomes too expensive and merging the functionality on the server side becomes increasingly complex, the transaction boundary (such as a service facade) is extended with additional methods to manage each particular excerpt of the whole graph. This leads in general to an explosion of hard-to-maintain methods (a.k.a. God facades), a great deal of redundancy, and procedural code.
  • Dependent PDOs cannot be lazily loaded in detached state in the presentation tier. The server must know in advance which subgraph of interconnected objects needs to be transported to the client. Dedicated methods (for example, getCustomerWithAddress()) are introduced to overcome the problem.
  • Every change of the client's state must be merged with the server.
  • For some business logic, such as additional queries, a PDO may require access to the EntityManager. The EntityManager, however, is only available on the server and not the client.

The solution to these problems is to create a perfect "anti service facade" -- a gateway. Instead of hiding the PDOs behind a facade, just try to expose them to the adjacent layer as conveniently for the UI as possible. Allow the user to modify the PDOs directly without any indirection.

This solution contradicts the common J2EE principle that encapsulation is the only way to achieve maintainability. But that principle is only true for perfect abstractions and encapsulations, which are hard to find in real-world projects. The inverse strategy works even better for some use cases. Just get rid of any layer that is probably leaky anyway and expose the business logic directly to the presentation tier. Every change in the persistence layer's structure becomes immediately visible in the UI, which makes the implementation of feature requests easy.

Your presentation is coupled to the particular implementation of the business logic, but the concrete implementation is already encapsulated. JPA abstracts from the particular provider, and EJBs are nothing other than annotated POJOs. So the technology is pretty well hidden already. The concrete state and implementation of domain-specific logic are well encapsulated too -- that's the PDO's main responsibility.

Transaction handling

One challenge remains for the gateway: the implementation of consistent and concise transaction handling. In a SOA, transaction handling is straightforward: a transaction is started and committed before and after every invocation of service facade's (the boundary's) method. This strategy works well, because the whole implementation of the business logic resides behind the service facade and is executed atomically.

The gateway, however, exposes the PDOs directly to the presentation tier, so it loses the control over the PDOs, and of transactions in particular. Furthermore, allowing the user to access and modify the PDOs directly makes centralized transaction management with a stateless service facade impossible. You can modify the detached PDOs directly, and a gateway lacks fine-grained merge() methods.

The solution is to introduce state on the server side. A stateful gateway can keep the PDOs attached with an EntityManager declared as PersistenceContext.EXTENDED, as shown in Listing 9. The EntityManager needs a transaction only as a trigger to flush the changes to the database, which can be started by a method that overrides the class default.

Listing 9. A stateful gateway implementation

@Stateful
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@Local(OrderGateway.class)
public class OrderGatewayBean implements OrderGateway{

    @PersistenceContext(type=PersistenceContextType.EXTENDED)
    EntityManager em;

    private Load current;

    public Load find(long id){
       this.current = this.em.find(Load.class, id);
       return this.current;
    }

    public Load getCurrent() {
        return current;
    }

    public void create(Load load){
        this.em.persist(load);
        this.current = load;
    }

    public void remove(long id){
        Load ref = this.em.getReference(Load.class, id);
        this.em.remove(ref);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void save(){
        //nothing to do
    }

    public void update(){
        this.em.refresh(this.current);
    }
    
    @Remove
    public void closeGate(){
        
    }
}

The implementation of the gateway is simple. It is a stateful session bean with an EXTENDED EntityManager. All incoming transactions are suspended with TransactionAttributeType.NOT_SUPPORTED on the class level. This is only possible with an EXTENDED EntityManager, which can only be injected into a stateful session bean. An EntityManager with a default configuration injected to a stateless session bean would throw javax.persistence.TransactionRequiredException in most of its methods invoked without an active transaction.

The save() method overrides the class default with the REQUIRES_NEW transaction attribute, which forces the container to start a new transaction. This in turn causes the EntityManager to flush all attached entities into the database and eventually to send the commit to the database.

The gateway is stateful, so it can maintain client-specific state -- and a reference to a PDO. We cache the root Load PDO to make it more easily accessible for the gateway clients. The reference to the Load PDO is maintained in the find() and create() methods. The merge() method is not needed, because there are no detached PDOs to merge. All changed PDOs are automatically synchronized with the database at the end of the transaction.

The gateway is stateful, so there is 1:1 relation between the client and the gateway. This can be only achieved by injecting the gateway into a stateful Web component. If you are using JavaServer Faces (JSF), you can set up this relation by injecting the gateway into a session-scoped backing bean, as shown in Listing 10.

Listing 10. Declaration of a stateful (session-scoped) backing bean for gateway injection

<managed-bean>
    <managed-bean-name>SessionBean1</managed-bean-name>
    <managed-bean-class>.....SessionBean1</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The Web container stores a session-scoped backing bean in the javax.servlet.http.HttpSession, so the relation between the user session (an open browser window) and the gateway instance is correctly maintained already. You need only inject the session bean into a session-scoped backing bean using the @EJB annotation:

public class SessionBean1 {
    @EJB
    private OrderGateway orderGateway;

The gateway can claim a significant amount of memory. The total amount depends on the number of attached entities inside a user session. It might become a problem in applications with many concurrent sessions, so you should free the resources as soon as possible. The gateway's lifecycle is directly dependent on the lifecycle of the HttpSession. Destruction of the HttpSession should cause the immediate destruction of the gateway. You can achieve this easily by overriding the backing bean's destroy() method and invoking a @Remove annotated method:

@Override
public void destroy() {
    //closeGateway is annotated with @Remove
    this.orderGateway.closeGateway();
}
1 2 3 Page 2
Page 2 of 3