Domain-driven design with Java EE 6

An object-oriented architecture for interactive applications

For Java EE applications based on type-dependent algorithms, a domain-driven design that leverages object-orientation is better than a procedurally implemented service-oriented architecture. Adam Bien explains how and why object-oriented persistence makes your domain-driven application's code more efficient, maintainable, and testable. Level: Advanced

In "Lean service architectures with Java EE 6," I discussed how to implement a maintainable service-oriented architecture (SOA) that is based on anemic persistent objects and transient, stateless services. In this follow-up article, you'll learn about an architectural approach that is the exact opposite of that. What ties together the two architectures is that both can be implemented with Java EE 6 and, in particular, Enterprise JavaBeans (EJB).

As I'll explain in this article, you can build domain-driven applications with "real" objects -- objects that comprise encapsulated state and well-defined behavior. Whereas SOA is stateless and tries to hide a component's implementation, domain-driven applications are mostly stateful and aim to expose the domain objects to the user as directly and conveniently as possible.

Procedural vs domain-driven design

The vast majority of J2EE and Java EE applications are built in a procedural way. The business logic is decomposed into tasks and resources, which map, respectively, to services and anemic, persistent entities. Such an approach works surprisingly well until type-specific behavior for domain objects must be realized. Then the lack of object orientation in the persistence layer can cause problems.

Attempts to implement object-oriented algorithms with procedural techniques end up in many instanceof checks and/or lengthy if-statements. Such type checks are required because in the SOA world, domain objects are anemic, so inheritance doesn't pay off. Even if inheritance is used to design the domain model, object orientation's most powerful feature -- polymorphic behavior -- isn't leveraged at all.

The anemic domain model forces you to implement type checks outside the anemic entities, in services or service facades. To make their internal state accessible to the services and service facades, the anemic structures (technically they are not objects, by definition) must expose their internal state to the outside world too -- through field accessors (getters/setters) -- requiring a lot of plumbing.

In data-driven use cases such as master data management (CRUD), you can implement workflow-related, procedural behavior efficiently with service facades and few anemic entities. A domain-driven architecture, on the other hand, is perfect for more-complex, type-dependent algorithms in more-interactive applications. Most nontrivial Java EE projects require both approaches.

Essential ingredients of domain-driven design

The crucial artifact in any domain-driven application is a domain object. A domain object can be considered a real-world artifact in the given project context. The challenge is to grasp the context and not try to model a perfect, theoretical world. Pragmatism rules. Domain objects are just cohesive classes with encapsulated state and related behavior. They use inheritance naturally, where appropriate.

Domain objects represent important concepts in the target domain and so must often be persistent. The Java Persistence API (JPA) turns out to be quite flexible for mapping rich domain objects to relational tables. The more complex the logic you need to realize, the more easily object-oriented persistence can be maintained and developed.

The real issue with complex logic realized with anemic structures are type distinctions in the service layer. Massive if-statements might be necessary to differentiate among entity types. Every introduction of a new subclass, or even a change in the existing business logic, requires you to find, enhance, and test these type checks. For example, the computation of shipping costs dependent on weight and the OrderItem type would look like Listing 1.

Listing 1. Procedural approach to switching between entity types

@Stateless
public class ShipmentService {
  public final static int BASIC_COST = 5;

  @PersistenceContext
  private EntityManager em;

     public int getShippingCosts(int loadId) {
        Load load = em.find(Load.class, loadId);
        return computeShippingCost(load);
    }

    int computeShippingCost(Load load){
        int shippingCosts = 0;
        int weight = 0;
        int defaultCost = 0;
        for (OrderItem orderItem : load.getOrderItems()) {
            LoadType loadType = orderItem.getLoadType();
            weight = orderItem.getWeight();
            defaultCost = weight * 5;
            switch (loadType) {
                case BULKY:
                    shippingCosts += (defaultCost + 5);
                    break;
                case LIGHTWEIGHT:
                    shippingCosts += (defaultCost - 1);
                    break;
                case STANDARD:
                    shippingCosts += (defaultCost);
                    break;
                default:
               throw new IllegalStateException("Unknown type: " + loadType);
            }
        }
        return shippingCosts;
    }
}

In Listing 1, all the logic resides in a service or service facade and consists mainly of type checks. The service must differentiate among the different types to apply type-dependent computational algorithms.

The same logic can be expressed with only a fraction of code in an object-oriented way, as shown in Listing 2. You can even eliminate the service, because the logic is implemented in the domain object itself.

Listing 2. Polymorphic business logic inside a domain object, without type checks

@Entity
public class Load {

    @OneToMany(cascade = CascadeType.ALL)
    private List<OrderItem> orderItems;
    @Id
    private Long id;

    protected Load() {
        this.orderItems = new ArrayList<OrderItem>();
    }

    public int getShippingCosts() {
        int shippingCosts = 0;
        for (OrderItem orderItem : orderItems) {
            shippingCosts += orderItem.getShippingCost();
        }
        return shippingCosts;
    }
//...
}

In Listing 2, computation of the total costs is realized inside a rich domain object -- the persistent domain object (PDO) -- not inside a service. Object-oriented persistence makes the code more concise and so also easier to understand. Actual computation of type-specific costs is performed in concrete subclasses such as the BulkyItem class in Listing 3, which makes testing the computation outside the EntityManager much easier.

Listing 3. Type-specific computation in a subclass

@Entity
public class BulkyItem extends OrderItem{

    public BulkyItem() {
    }

    public BulkyItem(int weight) {
        super(weight);
    }

    @Override
    public int getShippingCost() {
        return super.getShippingCost() + 5;
    }
}

We can change the computation of the shipping cost of a BulkyItem without touching the remaining classes. And it's easy to introduce a new subclass without affecting the computation of the total costs in the Load class.

The creation of a PDO graph is easier and more intuitive, compared to the traditional getter/setter-driven approach shown in Listing 4.

Listing 4. The procedural way of object construction

Load load = new Load();
OrderItem standard = new OrderItem();
standard.setLoadType(LoadType.STANDARD);
standard.setWeight(5);
load.getOrderItems().add(standard);
OrderItem light = new OrderItem();
light.setLoadType(LoadType.LIGHTWEIGHT);
light.setWeight(1);
load.getOrderItems().add(light);
OrderItem bulky = new OrderItem();
bulky.setLoadType(LoadType.BULKY);
bulky.setWeight(1);
load.getOrderItems().add(bulky);

Instead of creating the anemic objects independently and wiring them together afterwards, you can take a concise object-oriented approach using the Builder pattern, as shown in Listing 5.

Listing 5. Concise PDO construction with the Builder pattern

Load build = new Load.Builder().
    withStandardItem(5).
    withLightweightItem(1).
    withBulkyItem(1).
    build();

The Builder pattern lets us construct the Load domain object with concrete OrderItems in convenient way. Method chaining saves some superfluous lines of code and allows autocompletion support in common IDEs. In addition, consistency checks are performed in the build() method, so it's not necessary to create empty Load objects.

Builder is implemented as a static inner class, as shown in Listing 6.

Listing 6. Builder realized as a static inner class inside a PDO

@Entity
public class Load {

    @OneToMany(cascade = CascadeType.ALL)
    private List<OrderItem> orderItems;
    @Id
    private Long id;

    protected Load() {
        this.orderItems = new ArrayList<OrderItem>();
    }

    public static class Builder {

        private Load load;

        public Builder() {
            this.load = new Load();
        }

        public Builder withBulkyItem(int weight) {
            this.load.add(new BulkyItem(weight));
            return this;
        }

        public Builder withStandardItem(int weight) {
            this.load.add(new StandardItem(weight));
            return this;
        }

        public Builder withLightweightItem(int weight) {
            this.load.add(new LightweightItem(weight));
            return this;
        }

        public Load build() {
            if (load.orderItems.size() == 0) {
                throw new IllegalStateException("...");
            }
            return this.load;
        }
    }

    void add(OrderItem item) {
        this.orderItems.add(item);

    }

    public int getShippingCosts() {
        int shippingCosts = 0;
        for (OrderItem orderItem : orderItems) {
            shippingCosts += orderItem.getShippingCost();
        }
        return shippingCosts;
    }

    public OrderItem lightest(){
      if(this.orderItems == null || this.orderItems.size() == 0)
            return null;
       Collections.sort(this.orderItems, new WeightComparator());
       return this.orderItems.iterator().next();
    }

    public OrderItem heaviest(){
      if(this.orderItems == null || this.orderItems.size() == 0)
            return null;
       Collections.sort(this.orderItems, new WeightComparator());
       Collections.reverse(orderItems);
       return this.orderItems.iterator().next();
    }

    public OrderItem dropHeaviest(){
        OrderItem heaviest = heaviest();
        if(heaviest != null)
            drop(heaviest);
        return heaviest;
    }

    public OrderItem dropLightest(){
        OrderItem lightest = lightest();
        if(lightest != null)
            drop(lightest);
        return lightest;
    }

    public Load drop(OrderItem orderItem){
        this.orderItems.remove(orderItem);
        return this;
    }
    public Long getId() {
        return id;
    }

    public int getNumberOfOrderItems(){
        return this.orderItems.size();
    }
}

The Builder class creates Load and provides methods with fluent names (such as withBulkyItem). Each method creates a corresponding subclass of OrderItem, adds it to the load, and returns the Builder instance itself. This lets us construct the Load with convenient method chaining.

The logic I've shown you so far is limited to the construction and computation of shipping costs. Even more interesting is the implementation of non-idempotent methods. The dropHeaviest() method removes the heaviest OrderItem from the Load. There's nothing exciting about that, but keep in mind that the PDOs are persistent entities. Every change to the state, and even to relations, is automatically synchronized with the persistence at the end of the transaction. Just imagine an object-oriented implementation of a Subversion (SVN) repository with JPA. A change to a versioned file must be propagated to all packages until the project root is reached. The whole path is marked as changed. A SVN commit at the project level causes a recursive descent through all changed folders until all changed files are committed as well. To implement such a cascading algorithm in object-oriented way would be straightforward, whereas a procedural approach would be error-prone and hard to maintain.

Testing PDOs

PDOs are just annotated classes, so you can test them just like usual POJOs. For the business-logic tests you don't even need to mock out or bootstrap the EntityManager. You can easily create the instances needed to perform the test in the @Before or @BeforeClass methods and use them in the actual test methods, as shown in Listing 7.

Listing 7. PDO business logic test -- without involving the EntityManager

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();
}

Rethinking J2EE best practices

The idea of a gateway became possible only recently, with the introduction of Java EE 5. In the J2EE world such a pattern wouldn't make any sense, because of the lack of inheritance and detaching capabilities in Container-Managed Persistence (CMP) 2.0.

The attempt to introduce such a pattern could become really interesting in real-world projects. You will probably experience some resistance. In the past, one of the important measurement points of a scalable architecture was the degree of coupling and number of layers. Such a metric applied to a gateway pattern would produce rather poor results. A gateway tries neither to encapsulate the PDOs nor to introduce another layer of indirection. The opposite is true: a gateway is glue between layers, because it tries to make access to the domain objects as easy as possible.

A gateway is the reaction to the "Leaky Abstraction" principle. No matter how hard you try to encapsulate a nontrivial implementation, it will always be leaky. It is therefore smarter to expose an already well-designed entity directly, instead trying to encapsulate it further. This is especially true for UI-driven projects. The customer drives the requirements from the UI perspective, so the implementation of a feature will affect the presentation tier first. Such changes are not local to the presentation tier and will hit the adjacent layers as well. The leakier the boundary between layers, the more beneficial will be the effects of introducing a gateway into your architecture.

The gateway's responsibilities

A gateway exposes PDOs directly to the presentation layer. The most important participant is the PDO and the UI itself. The gateway is just an intermediary and provides only the execution context for the PDOs. A gateway is responsible for:

  • Keeping the EntityManager open between method calls, so that the already known PDOs do not become detached.
  • As convenient and direct exposure of PDOs as possible.
  • Providing a defined synchronization point (the save() method ) and optional undo method (EntityManager#refresh).
  • Per-reference (local) access to the PDOs. This requires you to run the EJB container in the same JVM as your Web container. This is the default setting in most application servers.
  • Implementation of crosscutting operations and especially query methods.

A gateway, however, relies on some minimum set of PDO qualities as well:

  • PDOs should be designed to be directly accessed in the presentation. Their functionality should be well encapsulated.
  • A PDO's public methods should always leave the domain object in consistent state. This can't be achieved easily with plain getters and setters.

The presentation tier must be able to keep, or at least determine, user's state. Your Web framework should be capable of keeping the reference to the gateway associated with the user's session. This is already a given in JSF, can be easily achieved with Wicket, and is a little bit harder with plain servlets.

State, distribution, and scalability

The direct manipulation of domain objects without synchronization and lazy loading requires attached entities and direct per-reference access to the PDOs. Only then can the EntityManager flush the changed entities to the database. Working with always-attached entities in the UI layer also solves the lazy-loading problem. Although the entities are used outside the gateway, they remain attached. You don't need to preload the entities in advance or use fetch joins for that purpose in the gateway. Lazy loading will just work as expected.

Gateways are stateful, so every user gets his or her own gateway instance, which in turn is associated with an EntityManager. Each EntityManager instance maintains its own JPA entity cache. The EntityManagers are configured as PersistenceContext.EXTENDED, so the cache is not cleared after every transaction.

Furthermore, all PDOs will remain attached, in extreme cases for the length of the session. The size of PDOs in memory is hard to estimate in advance, but can be easily measured during a load test. Such tests do not have to be realistic; it's simply important to test the system's behavior under heavy load. A small proof of concept will help you better understand and estimate the application's scalability characteristics and hardware requirements. You can even attach a profiler to the server during the load test to get a sense of the system behavior.

Object-oriented persistence is not limited to use in stateful, server-centric environments. PDOs can be also used behind a stateless, remote service facade. In that case they remain attached for the length of the transaction and therefore the method invocation. After the invocation they become detached and must be reattached after every method call. Lazy loading and automatic synchronization between layers will not work and must be implemented manually. Usually you will not transfer the PDOs to the client. The invocation of business methods may lead to state changes in the PDOs that must be synchronized manually with the server. For that reason a stateless service facade already implies either the use of anemic persistent structures, or dedicated objects just for data transport between layers. Unfortunately this introduces a lot of plumbing and empty copy-logic.

In conclusion

Domain-driven design and service-oriented design are opposite approaches. Domain-driven design relies mainly on encapsulated objects with state and behavior, whereas SOA emphasizes the procedure (a service). Object-oriented applications tend to be stateful, SOA rather stateless. The best practices in one case become anti-patterns in the other.

Interestingly, SOA is a good match to SOAP services defined in Web Services Description Language (WSDL), which emphasize contracts and actions. In contrast, REST focuses on direct addressing and manipulation of resources, which is more compatible with the domain-driven approach.

This article is based on Adam Bien's forthcoming book: Real World Java EE Patterns: Rethinking Best Practices.

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 Real World Java EE Patterns: Rethinking Best Practices. Visit Adam's blog.

Learn more about this topic

  • The profiler shipped with NetBeans 6.5/6.7 supports remote profiling.

More from JavaWorld

Join the discussion
Be the first to comment on this article. Our Commenting Policies