Lean service architectures with Java EE 6

Elements and patterns of a lean SOA

1 2 Page 2
Page 2 of 2

The question to ask is how often you've had a concrete requirement to switch among "relational databases, object-oriented databases, flat files, and so forth" in a real-world project. In the absence of such a requirement, you should ask yourself how likely such a replacement of the data store really is. Even if it is likely to happen, data-store abstractions are rather leaky. JPA's EntityManager is comparable to a domain store and works with managed entities. Other data stores are more likely to use the "per value" approach and so return a new copy of a persistent entity on every request in a single transaction. This is a huge semantic difference; with the EntityManger, there's no need to merge attached entities back, whereas it is absolutely necessary in the case of a DAO with data-transfer objects.

Even a perfect database abstraction is leaky in practice. Different databases behave differently and have specific locking and performance characteristics, in particular. An application might run and scale perfectly on one database but encounter frequent deadlocks with another product. No abstractions or design patterns can neutralize such variations. The only possible way to survive such a migration is to tweak the isolation levels, transactions, and database configuration. These issues can't be hidden behind a DAO interface, and it's likely that the DAO implementation would not even change. The DAO abstraction would be only useful if the actual JPA-QL or SQL statements change, which is a minor problem.

So neither the original definition nor a database-migration use case justifies the implementation of a dedicated DAO. So are DAOs dead? For standard use cases, yes. But you still need a DAO in most applications to adhere to the principles of DRY (don't repeat yourself) and separation of concerns. It is more than likely that an average application uses a set of common database operations over and over again. Without a dedicated DAO, the queries will be sprinkled through your business tier. Such duplication significantly decreases the application's maintainability. But the redundant code can be easily eliminated with a DAO that encapsulates the common data-access logic.

DAOs aren't dead, but they cannot be considered as a general best practice any more. They should be created in a bottom-up, rather than a top-down, fashion. If you discover data-access code duplication in your service layer, just factor it out to a dedicated DAO and reuse it. Otherwise it is just fine to delegate to an EntityManager from a service. The enforcement of an empty DAO layer is even more harmful, because it requires you to write dumb code for even simple use cases. The more code is produced, the more time you must spend to write tests and to maintain it.

With JDK 1.5 and the advent of generics, it is possible to build and deploy a generic, convenient, and typesafe DAO once and reuse it from variety of services. Such a DAO should come with a business interface to encourage unit testing and mocking. Listing 4 shows an example of such an interface.

Listing 4. DAO business interface

public interface CrudService {
    public <T> T create(T t);
    public  <T> T find(Class<T> type,Object id);
    public  <T> T update(T t);
    public void delete(Object t);
    public List findWithNamedQuery(String queryName);
    public List findWithNamedQuery(String queryName,int resultLimit);
    public List findWithNamedQuery(String namedQueryName, Map<String,Object> parameters);
    public List findWithNamedQuery(String namedQueryName, Map<String,Object> parameters,int resultLimit);
}

However, the business interface is optional in EJB 3.1. The DAO interface must be executed only in the context of an already existing transaction, so it is @Local and comes with the MANDATORY transaction setting

Instead of polluting this POJI with a @Local annotation, you expose it by the session bean implementation instead. The bean implementation itself delegates to the EntityManager only in simple cases. You should increase the DAO's value and encapsulate reusable, project-specific queries in it. Listing 5 shows a generic DAO, realized as a service.

Listing 5. Generic DAO implemented as a service

@Stateless
@Local(CrudService.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class CrudServiceBean implements CrudService {
   
    @PersistenceContext
    EntityManager em;
    
    public <T> T create(T t) {
        this.em.persist(t);
        this.em.flush();
        this.em.refresh(t);
        return t;
    }

    @SuppressWarnings("unchecked")
    public <T> T find(Class<T> type,Object id) {
       return (T) this.em.find(type, id);
    }

    public void delete(Object t) {
       Object ref = this.em.getReference(t.getClass(), t);
       this.em.remove(ref);
    }

    public <T> T update(T t) {
        return (T)this.em.merge(t);
    }

    public List findWithNamedQuery(String namedQueryName){
        return this.em.createNamedQuery(namedQueryName).getResultList();
    }
    
    public List findWithNamedQuery(String namedQueryName, Map<String,Object> parameters){
        return findWithNamedQuery(namedQueryName, parameters, 0);
    }

    public List findWithNamedQuery(String queryName, int resultLimit) {
        return this.em.createNamedQuery(queryName).
                setMaxResults(resultLimit).
                getResultList();    
    }

    public List findByNativeQuery(String sql, Class type) {
        return this.em.createNativeQuery(sql, type).getResultList();
    }
    
   public List findWithNamedQuery(String namedQueryName, Map<String,Object> parameters,int resultLimit){
        Set<Entry<String, Object>> rawParameters = parameters.entrySet();
        Query query = this.em.createNamedQuery(namedQueryName);
        if(resultLimit > 0)
            query.setMaxResults(resultLimit);
        for (Entry<String, Object> entry : rawParameters) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
        return query.getResultList();
    }
}

More challenging are the query methods. You can't know the number of query parameters and their names when you develop the DAO service, so you must provide them in a generic way. Listing 6 shows the creation of query parameters in a fluent way with the Builder pattern.

Listing 6. The builder for query parameters

public class QueryParameter {
    
    private Map<String,Object> parameters = null;
    
    private QueryParameter(String name,Object value){
        this.parameters = new HashMap<String,Object>();
        this.parameters.put(name, value);
    }
    public static QueryParameter with(String name,Object value){
        return new QueryParameter(name, value);
    }
    public QueryParameter and(String name,Object value){
        this.parameters.put(name, value);
        return this;
    }
    public Map<String,Object> parameters(){
        return this.parameters;
    }
}

The most suitable data structure for the transportation of query parameters is a java.util.Map, but it's rather inconvenient to create. The simple QueryParameter builder makes the construction of the query parameters more convenient and fluent. Listing 7 shows the use of the QueryParameter builder to construct the parameters.

Listing 7. The fluent way to construct a query parameter

import static ...dataservice.QueryParameter.*;

   public List<Order> findOrdersForCustomer(int customerId){
        return this.crudService.findWithNamedQuery(Order.findByCustomerId,
                with("customerId", customerId).
                parameters());

The static with() method creates a new QueryParameter instance. It enables a static import and makes the qualification with the QueryParameter superfluous. The and() method just puts the parameters into the internal Map and returns the QueryParameter again, which makes the construction fluent. Finally, the method parameters return only the internal Map, which can be directly passed to the CrudService.

A touch of pragmatism

Most service components are not complicated and consist mainly of basic data operations. In these cases the facade would just delegate to a service, which in turn delegates to the javax.persistence.EntityManager or a generic CrudService to perform its persistence tasks. Otherwise you will end up getting two dumb delegates: the facade would only delegate to the service, and the service to the CrudService (aka DAO). Delegates without additional responsibilities are just dead code. They only increase the code complexity and make maintenance more tedious. You could fight this bloat by making the service layer optional. The facade would manage the persistence and delegate the calls to the EntityManager. This approach violates the separation-of-concerns principle, but it's a very pragmatic one. It is still possible to factor reusable logic from the facade into the services with minimal effort.

If you really want to encapsulate the data access in a dedicated layer, you can easily implement it once and reuse the generic access in different projects. Such a generic DAO does not belong to a particular business component. It would violate the cohesion. In our case it is deployed in a technical component with the name dataservice.

Diagramming robustness

The architecture I've described in this article can be expressed with a robustness diagram consisting of Entity, Control, and Boundary (as defined on the Agile Modeling site). The original definition of the Entity-Control-Boundary (ECB) architectural pattern matches perfectly with our pattern language. A domain structure is an Entity, the Control is a service, and the Boundary is realized with a facade. In simpler cases the facade and service can collapse, and a service would be realized only as a facade's method in that case. Figure 3 shows the service component visualized with a robustness diagram:

Figure 3: The service component in a robustness diagram
Figure 3. The service component in a robustness diagram

We could even replace our custom naming in the internal-component layer with the names of the ECB elements. (The naming of Java EE components is mainly influenced by the Core J2EE Patterns, so the terms facade, service, and domain object are more typical in an "enterprise" Java context.)

As lean as possible, but not leaner

It would be hard to streamline the SOA architecture presented in this article further without sacrificing its maintainability. I know of no other framework that lets you express a service-based architecture in such a lean and straightforward manner. No XML configuration, external libraries, or specific frameworks are needed. The deployment consists of only a single JAR file with a short persistence.xml like the example shown in Listing 5.

Listing 5. persistence.xml -- the only XML configuration needed

<persistence>
  <persistence-unit name="prod" transaction-type="JTA">
    <jta-data-source>jdbc/sample</jta-data-source>
  </persistence-unit>
</persistence>

The whole architecture is based on few annotated Java classes with pure interfaces (that is, interfaces that are not dependent on the EJB API). With EJB 3.1 you could even remove the interfaces and inject the bean implementation directly. Services and DAOs could be deployed without any interfaces, which would halve the total number of files and make the component significantly leaner. However, you would lose the ability to mock out the service and DAO implementation easily for plain unit tests.

Consultant and author Adam Bien is an Expert Group member for the Java EE 6, EJB 3.1, and JPA 2.0 JSRs; a NetBeans Dream Team Member; and a Java Champion. He has edited several books about Java and J2EE technology. He is an architect and developer on Java EE 5 projects and participates in several open source projects as committer and owner. He is working on Pragmatic Java EE - Rethinking the Best Practices. Visit Adam's blog.

Learn more about this topic

More from JavaWorld

This story, "Lean service architectures with Java EE 6" was originally published by JavaWorld.

Copyright © 2009 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2