Lean service architectures with Java EE 6

Elements and patterns of a lean SOA

Although Java EE 6 is far less complex than previous platform versions, it can still be misused to create exaggerated and bloated architectures. In this article, Adam Bien delineates the essential ingredients of a lean service-oriented architecture (SOA), then explains how to implement it without sacrificing maintainability. Level: Intermediate

The complexity and bloat often associated with Java EE are largely due to the inherent complexity of distributed computing; otherwise, the platform is surprisingly simple. As I discussed in my last article for JavaWorld, Enterprise JavaBeans (EJB) 3.1 actually consists of annotated classes and interfaces that are even leaner than classic POJOs; it would be hard to find anything more to simplify. Nonetheless, (mis)use of Java EE can lead to bloated and overstated architectures. In this article, I discuss the essential ingredients of a lean service-oriented architecture (SOA), then explain how to implement one in Java EE without compromising maintainability. I'll start by describing aspects of SOA implementation that lend themselves to procedural programming, then discuss domain-driven (aka object-oriented) design.

SOA: The essential ingredients

The crucial artifact in any SOA is an exposed service. A service can be considered as a contract with significant business value. A clear relationship should exist between the service contract and a business requirement, a use case, or even a user story. A service can be realized as a single action or a set of cohesive actions. For example, an OrderService might comprise a single action that performs the actual order, or a group of related operations that also include canceling the order and receiving the order status. An SOA does not reveal any details about how a service must be implemented; it aims for services to be technology- and even location-agnostic.

SOA principles can be mapped to Java EE artifacts. The most important ingredients in a Java-based SOA implementation are interfaces and packages. Everything else is only a refinement, or design. Only one artifact in the language -- a plain Java interface -- meets the requirements of a service contract. It is often used to decouple the client from the service implementation. It can also be used to expose the functionality of a component.

Component based design (CBD) is SOA's evolutionary predecessor. Component contracts are comparable to SOA's services, except for their heavier dependence on particular technology and general lack of operations-related metadata -- such as service-level agreements (SLAs) -- or strong governance principles. A component is built on the maximal cohesion, minimal coupling principle. A component should be highly independent of other components, and the implementation should consist of related (cohesive) elements. In a Java EE SOA implementation, a component is a Java package with these strong semantics. The package's functionality is exposed with a single interface, or in rare cases a few interfaces.

And the essential complexity

SOA implies distribution of services, which is not always necessary or desirable in a Java EE application. It would be foolish to access a local service remotely just to satisfy some high-level SOA concepts. Direct local access should always be preferred over remote services. A local call is not only orders of magnitude faster than a remote one; the parameters and return values can also be accessed "per reference" and need not be serialized.

Whether the service is local or remote, the business logic should always be executed consistently. A component needs a dedicated remoting and transaction boundary interface, which acts as a gatekeeper (see Figure 1). The main responsibility of such a facade is to keep the granularity of the methods coarse and the persistent state of the component consistent.

Figure 1. Remoting / transactions boundary
Figure 1. Remoting/transactions boundary

You can achieve the right granularity only by carefully crafting the interface so that consistency can be easily ensured with built-in transactions. Listing 1 shows how to configure declarative transactions and expose a remote business interface.

Listing 1. Declarative transaction and remoting declaration

package ...bookstore.business.ordermgmt.facade;

@Stateless
@Remote(OrderService.class)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class OrderServiceBean implements OrderService {
    @EJB
    private CrudService crudService;    
    @EJB
    private VatCalculator vatCalculator;
    @EJB
    private PriceCalculator pc;
    @EJB
    private OrderProcessor op;
    @EJB
    private ShipmentService shipmentService;
  
    
public Shipment orderBook(int customerId,int isbn){
  BigDecimal priceNoVat = this.pc.computePrice(customerId, isbn);
  BigDecimal price = this.vatCalculator.computeVat(priceNoVat);
  Order o = this.op.processOrder(customerId, customerId, price);
  return this.shipmentService.deliver(o);
}
    
public Shipment findShipment(long shipmentId){
         return this.shipmentService.find(shipmentId);
}
//some methods omitted
}

Applying the @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) annotation to the class causes all methods to inherit this setting automatically. You could, alternatively, rely on the default (which is TransactionAttributeType.REQUIRED) and not specify a transaction setting. However, the OrderService is the transaction and remoting boundary. It is accessed exclusively by the UI, which must not start any transactions. Using the REQUIRES_NEW attribute is more explicit. It always starts a new transaction, which is what you would expect from a boundary. The REQUIRED configuration, if it were invoked without a transaction context, would reuse an existing transaction or start a new one. A boundary, however, will never be invoked in an existing transaction, which makes the default setting more confusing.

The @Remote annotation is applied to the bean class -- not the business interface -- which may look strange at the first glance. The business interface does not follow coding conventions either: its name doesn't contain the Remote suffix. As a result, the service consumer sees only a plain old Java interface and is not directly aware of using an EJB. Only an indirect dependency on unchecked exceptions is present. If you are building a Web application, the OrderServiceBean would be exposed with a local business interface only, whereas a rich client runs in a separate process and requires a remote interface. In either case there is no need to further emphasize the business interface's distributive capabilities. It gets injected or looked up, and the consumer is only interested in the functionality and not its remote visibility.

Separation of concerns, or divide and conquer

Listing 1's OrderServiceBean emphasizes the business logic and hides the component's implementation. These are the responsibilities of a typical Facade pattern. Furthermore, the OrderServiceBean ensures the component's consistency by starting a transaction before every method is exposed over the business interface. Transactions are a cross-cutting concern -- an aspect -- already implemented by the EJB container. Implementing both the controller logic and the actual business logic would be too much responsibility for a single facade.

The intention of a service is straightforward -- it is the realization of the business logic. In the SOA world it has rather procedural nature. A service resides behind the facade, so it can never be accessed directly by the UI or other presentation components. A service is stateless and can be only called by the facade. Every facade's method starts a new transaction, so a service can safely rely on the transactions' existence. Listing 2 shows the transaction and remoting configuration of a service.

Listing 2. Structure of a service

@Stateless
@Local(OrderProcessor.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class OrderProcessorBean implements OrderProcessor {
    
    @PersistenceContext
    private EntityManager em;

 public Order processOrder(int customerId, int productId, BigDecimal price) {
        Order order = new Order(customerId, productId, price);
        this.em.persist(order);
        return order;
    }
}

In Listing 2, the concerns are clearly separated. The facade provides the cross-cutting functionality and plays the controller role, and the service focuses on the actual domain-logic implementation. The clearly separated roles and responsibilities make it possible to predefine a service's structure and configuration easily. A service is a stateless session bean with local business interface. It is always invoked by the facade in a transaction, so it can be deployed with the MANDATORY setting. This restrictive TransactionAttribute further emphasizes the encapsulation; it is not possible to call it directly without a transaction. The bean implementation exposes the business interface with the @Local annotation, so the interface is independent of the EJB API.

Domain objects or structures?

Since services implement the actual business logic, and the facade cares about the cross-cutting concerns, there is no more functionality left to be implemented in the domain objects. The Java Persistence API (JPA) entities consist only of annotated attributes and getters/setters; they contain no business logic, as you can see in Listing 3.

Listing 3. The DAO implementation

@Entity
@Table(name="T_ORDER")
@NamedQueries({
@NamedQuery(name="",query="SELECT o FROM Order o where o.customerId = :customerId")
})
public class Order {

 public final static String PREFIX ="..ordermgmt.domain.Order";
 public final static String findByCustomerId = PREFIX + "findByCustomerId";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private int customerId;
    private int productId;
    private double price;
    
    public Order() { }
    
    
    public Order(int customerId,int productId,BigDecimal price){
        this.customerId = customerId;
        this.productId = productId;
        this.price = price.doubleValue();
    }

    public Long getId() {
        return id;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getProductId() {
        return productId;
    }

    public void setProductId(int productId) {
        this.productId = productId;
    }
}

Although the anemic object model is considered to be an antipattern, it fits very well into a SOA. Most of the application is data-driven, with only small amount of object-specific or type-dependent logic. For simpler applications, anemic JPA entities have advantages too:

  • Dumb entities are easier to develop. No domain knowledge is required.
  • Anemic JPA entities can be generated more easily. The necessary metadata can be entirely derived from the database. You can just maintain the database, and the existing entities can be safely overwritten by newly generated ones.
  • The persistent entities can be detached and transferred to the client. Aside from the classic "lazy loading" challenges, there are no further surprises. The client will only "see" the accessors and will be not able to invoke any business methods.

Thinking about internal structure

I've already identified some key ingredients of a service-oriented component:

  • Facade: Provides simplified, centralized access to the component and decouples the client from the concrete services. It is the network and transaction boundary.
  • Service: The actual implementation of business logic.
  • Domain structure: This is a structure rather than an object. It implements the component's persistence and exposes all of its state to the services, without encapsulation.

You will find these three layers in most components. The component structure manifests itself as internal packages named by the key ingredients. The ordermgmt component consists of the facade, service, and domain packages, as shown in Figure 2.

Figure 2. Internal component layering with packages
Figure 2. Internal component layering with packages

This convention not only standardizes the structure and improves maintainability, but also allows automatic dependency validation with frameworks like JDepend, Checkstyle,  Dependometer, SonarJ, or XRadar. You can even perform the validation at build time. If you do, the continuous build would break on violation of defined dependencies. The rules are clearly defined with strict layering: a facade may access a service, and the service a domain object, but not vice versa.

DAOs: Dead or alive?

The original context of a Data Access Object (DAO), as defined in the Core J2EE pattern catalog (the emphasis is mine), is:

Access to data varies depending on the source of the data. Access to persistent storage, such as to a database, varies greatly depending on the type of storage (relational databases, object-oriented databases, flat files, and so forth) and the vendor implementation.

The motivation for this pattern was the desire to achieve greatest possible decoupling from the concrete realization of the data-store mechanics.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more