The EJB Advocate: Is it ever best to use EJB components without facades in service oriented architectures?

The EJB Advocate evaluates using various forms of "facades," including POJOs, HttpServlets, session EJB components, message driven beans, and entity EJB Home methods, in an attempt to get at the heart of what makes up a good service oriented architecture implemented with J2EE™ components.

Share:

Geoff Hambrick (ghambric@us.ibm.com), Distinguished Engineer, IBM

Geoff HambrickGeoff Hambrick is a lead consultant with the IBM Software Services for WebSphere Enablement Team and lives in Round Rock, Texas (near to Austin). The Enablement Team generally helps support the pre-sales process through deep technical briefings and short term Proof of Concept engagements. Geoff was appointed an IBM Distinguished Engineer in March of 2004 for his work in creating and disseminating best practices for developing J2EE applications hosted on IBM WebSphere Application Server.



15 June 2005

Also available in Russian

From the IBM WebSphere Developer Technical Journal.

In each column, The EJB Advocate presents the gist of a typical back-and-forth dialogue exchange with actual customers and developers in the course of recommending a solution to an interesting design issue. Any identifying details have been obscured, and no "innovative" or proprietary architectures are presented. For more information, see Introducing the EJB Advocate.

EJB components unmasked

In all of the previous EJB Advocate articles, the EJB components have been wrappered behind a facade of some type. For example, in the article about EJB cross references, the developers of that team used pure Java (POJO) wrappers in front of their session beans to hide the complexity of looking up homes and creating the reference from the client. And the session beans themselves were even facades around POJO helper classes to hide the complexity of implementing EJBs from the service providers. In short, the goal for that team was to hide the fact that EJBs were being used at all. Figure 1 illustrates their use of POJOs as an encapsulation layer.

Figure 1. UML Diagram showing use of POJOs as an encapsulation layer
Figure 1. UML Diagram showing use of POJOs as an encapsulation layer

In the two articles (Part 1 and Part 2) about making CMP entities perform, the recommendation was to always use session EJB methods passing data transfer objects (DTOs) in and out as a facade around local CMPs -- both to insure that there is a global transaction and to minimize the chattiness between layers. Figure 2 shows how the session facade mediates between client (HttpServlet) and data layers (entity EJB components), and thus provides a service oriented architecture.

Figure 2. UML Diagram showing session EJB as service oriented mediators
Figure 2. UML Diagram showing session EJB as service oriented mediators

That said, when we introduced this EJB Advocate series of articles, we ended with the proverb that there are no bad patterns, only bad applications of patterns. Said another way, this proverb is an admonition that one should never say "never" or "always". Forgetting for a moment that I just said never, the following discussion illustrates what I really mean is that there are some cases where a J2EE component is best used directly, and some where they are best used indirectly (through a facade of some type). Until this discussion, I could not clearly articulate the reasons why one would choose one over the other.


The problem: too many components

divider

Dear EJB Advocate,

Our team was glad to see in your last article that you finally showed a real object oriented approach to using entity EJB components. Coming from a Smalltalk shop that was forced to switch to Java™, we have been more than a little frustrated that J2EE developers, for the most part, have resorted to procedural coding style.

But object oriented or not, if you take all of your articles together, you get one giant boatload of classes to build. Starting from the client:

  1. HttpServlet -- to accept the request from the browser and invoke the associated service
  2. Java Server Page -- to render the result from service
  3. Service Delegate -- to encapsulate whether a session EJB is used or not
  4. Service Session EJB Home -- to get the remote reference to a session bean
  5. Service Session EJB Interface -- to hide the stub implementation from the delegate
  6. Service Session EJB Bean -- to provide distribution, transactions, and security
  7. Service POJO -- the business logic that determine the appropriate task(s) to execute
  8. Task Delegate -- to encapsulate whether a session EJB is used or not
  9. Task Session EJB Home -- to get the local reference to a session bean
  10. Task Session EJB Interface -- to hide the local EJB implementation from the delegate
  11. Task Session EJB Bean -- to provide distribution, transactions, and security
  12. Task POJO -- the business logic of the task (assuming this is the entity facade)
  13. Business Object Delegate -- to encapsulate whether an entity EJB is used or not
  14. Business Object Key -- the primary key (or query object) for a given data object
  15. Business Object View -- a data transfer object minimizing calls between layers
  16. Entity EJB Home -- to get the local reference for an Entity EJB
  17. Entity EJB Interface -- to hide the local CMP implementation from the delegate
  18. Entity EJB Implementation -- the logic associated with the data layer

This list just includes the basics for an application with just a single service, task, and business object. It does not even include the deployment descriptors and all the code that gets generated by the deployment tools!

Now, imagine a more complex application, like that using the data model you showed in the last article. There are likely to be a number of "services", "tasks" and "business objects" that have a number of customized data transfer objects at each layer to handle the transformations. We don't have the code generators that Cross seemed to have. Therefore, we are more inclined to skip all the delegates and use the "right" EJB components as directly as possible, but do not want to head down the wrong road.

Please sign us,
Too Many Notes

divider


Tradeoffs to consider when using delegates, facades, and helpers

divider

Dear Too Many,

Stay tuned for an article on building and using code generators with our Eclipse-based tools. But even though these tools will help you generate many of these components "for free" from a model of your application, having unnecessary components at run time is not good for efficiency. Code generators can actually make the problem worse! On the other hand, if they are easy to modify, they can just as easily fix the problem.

So, as for having to build all of these components by hand, it is always strange to be in a position to defend architectures that you don't always agree with. Take the use of the delegate classes to "completely hide the fact that EJBs are being used" as described by Cross with EJB References in the first article, which mentioned that there are tradeoffs to consider, but did not elaborate. It seems like now is the time. So I apologize for a long reply in which I ultimately agree with your position.

Hopefully, this discussion will help you clarify your position too.

Different EJB component types have different access methods
Your list above shows three distinct types of components that are being used in the end-to-end architecture:

  • remote services
  • local tasks, and
  • business objects.

Each of these is associated with a different kind of EJB component or interface style (remote session EJB, local session EJB, and local entity EJB, respectively). The code to retrieve each is different. For example, here is an HttpServlet code snippet to invoke the getCustomerWithOpenOrder() method on a remote session EJB facade called OrderEntry (we described the details of this method implementation in the previous EJB Advocate article). It includes both the normal and error path processing to show the true complexity:

Listing 1. Invoking a remote session bean
try {
    // Assume a routine to get the id exists on the superclass servlet
    int cID = getCustomerId(req);

    // The five lines of code to invoke a remote session method
    InitialContext initCtx = new InitialContext();
    Object obj = initCtx.lookup("java:comp/env/ejb/OrderEntry");       
    OrderEntryHome home = (OrderEntryHome)PortableObjectRemote(
	obj, OrderEntryHome.class
    );       
    OrderEntry ref = home.create();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Assume an include JSP method exists on the superclass servlet
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Occurs if parse routine fails to get a valid positive integer
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Occurs if JNDI context cannot be initialized or name not found
    include("NamingException", e, req, res);		
}
catch (RemoteException e) {
    // Occurs if the object cannot be narrowed, created or executed
    include("RemoteException", e, req, res);		
}
catch (CustomerNotFoundException e) {
    // Occurs if the customer ID is valid integer but not found
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Occurs if the customer has no open order
    include("OrderNotOpenException", e, req, res);
}

Here is a similar snippet for a local session:

Listing 2. Invoking a local session bean
try {
    // Assume a routine to get the id exists on the superclass servlet
    int cID = getCustomerId(req);

    // The four lines of code to invoke a local session method
    InitialContext initCtx = new InitialContext();
    OrderEntryHome home = (OrderEntryHome)initCtx.lookup(   
        "java:comp/env/OrderEntry"
    );       
    OrderEntry ref = home.create();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Assume an include JSP method exists on the superclass servlet
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Occurs if parse routine fails to get a valid positive integer
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Occurs if JNDI context cannot be initialized or name not found
    include("NamingException", e, req, res);		
}
catch (ClassCastException e) {
    // Occurs if the object cannot be cast to the home class
    include("ClassCastException", e, req, res);		
}
catch (CustomerNotFoundException e) {
    // Occurs if the customer ID is valid integer but not found
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Occurs if the customer ID has no open order
    include("OrderNotOpenException", e, req, res);
}

The difference here is that you no longer need to check for remote exceptions, and the "narrow" is replaced with a simple cast operator.

Now, contrast the local session code with that needed to invoke the equivalent method implemented on a (local) Customer entity EJB:

Listing 3. Invoking a local entity bean
try {
    // Assume a routine to get the id exists on the superclass servlet
    int cID = getCustomerId(req);

    // The five lines of code to invoke a local entity method
    InitialContext initCtx = new InitialContext();
    CustomerHome home = (CustomerHome)initCtx.lookup(   
        "java:comp/env/Customer"
    );
    CustomerKey key = new CustomerKey(cID);
    Customer ref = home.findByPrimaryKey(key);
    CustomerData data = ref.getOpenOrder();

    // Assume an include JSP method exists on the superclass servlet
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Occurs if parse routine fails to get a valid positive integer
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Occurs if JNDI context cannot be initialized or name not found
    include("NamingException", e, req, res);		
}
catch (ClassCastException e) {
    // Occurs if the object cannot be cast to the home class
    include("ClassCastException", e, req, res);		
}
catch (FinderException e) {
    // Occurs if the customer is not found
    include("FinderException", e, req, res);	
}
catch (OrderNotOpenException e) {
    // Occurs if the customer ID is invalid
    include("OrderNotOpenException", e, req, res);
}

The difference between a local session and local entity is the need to create a key instance, use a custom finder, and handle the FinderException that can result.

As a tradeoff, notice that there is no need to pass in the customer ID to getOpenOrder() method (since the component represents the customer associated with the ID). And notice that its name does not need to specify "ForCustomer" to differentiate it from other possible methods on a session facade.

EJB components benefit from service oriented signatures
Regardless of the differences, each method returns a CustomerData object with an associated OrderData object. Each OrderData object is associated with zero or more LineItemData objects. Returning a complete "tree" of data transfer objects from a set of parameters (also possibly a tree of DTOs) makes the architecture more service oriented by minimizing the chattiness between the layers. In other words, one coarse grained call is all that is needed to gather the data needed in the HttpServlet above. Passing DTOs in and out (including for exceptions) makes the binding between client and service stateless, (also called "disconnected") even if an entity EJB is used to implement it because the next request will use the customer ID to retrieve the associated entity.

The fact that DTOs are serializable makes it possible to transform them into other forms using other services, called mediators. For example, the JSP in the above code can be thought of as a mediator to render the CustomerData into HTML associated with a Web page. If the component is used to implement a Web service, the gateway could associate a mediator to transform the CustomerData into an XML document that is part of a SOAP/HTTP reply to a non-J2EE client.

Good service oriented architectures provide a loose coupling between the client code such that the implementation can be adapted to current conditions without changing the client code. The simplest example of loose coupling is coding the service so that its implementation can be remote or local depending on the current configuration. For a more complex example, imagine that the submit() service implementation needs to be different depending on whether the customer is categorized as gold, silver, bronze, or unspecified. The Home of any version of EJB that supports the same bean interface can be bound into the JNDI namespace. The name is designed to include both the bean type and the category. At run time, the category is appended to the name to retrieve the right implementation transparently to the client code.

Delegate patterns can significantly simplify the client code
Another benefit of well designed service oriented signatures, regardless of the EJB component type used to implement them, is that a delegate (or service locator) pattern can be employed. For example, the template inheritance pattern discussed in February's article could be used to create a common superclass HttpServlet method that caches the EJB home (or even the session reference), and handles errors in a common way.

But a template superclass is simply one form of a delegate (or service locator) pattern. We discussed others in previous articles but did not show the code.

For example, the commonly used delegate pattern we discussed in the January article uses a separate Java class that is accessed through the new operator as follows:

Listing 4. Invoking a service through a delegate using the new operator
try {
    // Assume a routine to get the id exists on the superclass servlet
    int cID = getCustomerId(req);

    // The two lines of code to invoke the service
    OrderEntryDelegate ref = new OrderEntryDelegate();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Assume an include JSP method exists on the superclass servlet
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Occurs if parse routine fails to get a valid positive integer
    include("ParseException", e, req, res);
}
catch (ServiceException e) {
    // Occurs if the service is not able to be invoked 
    include("CustomerNotFoundException", e, req, res);
}
catch (CustomerNotFoundException e) {
    // Occurs if the customer ID is valid integer but not found
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Occurs if the customer ID has no open order
    include("OrderNotOpenException", e, req, res);
}

This snippet shows that as you minimize the "moving parts", the lines of code that have to be written in both the normal and error paths are reduced accordingly. As long as the number of potential clients that would use the delegate class is greater than one, the tradeoff appears to be worth it so far.

But the benefits of using a delegate go beyond making it easier for the client to access the implementation component. The client can simply call the service needed -- be it a composed service, a primitive task, or access to a single business object -- eliminating unnecessary transformations or passthrough layers. To put it another way, a service method is only implemented if it provides some kind of transform needed by the client. The choice of which EJB component to use is completely hidden from the client -- if EJBs are used at all.

Care must be taken not to reinvent the J2EE framework with delegates
One problem with the "new" approach to a delegate is that every time it is invoked, an instance of the delegate is created and initialized, taking extra time and generating extra garbage unless it is cached. If caching of the actual reference is used, it must be thread safe, especially if used in the context of a multi-threaded client -- like an HttpServlet.

An alternative approach that handles instance caching automatically is the singleton pattern, which is implemented using a static method and member variable on the class. This method acts like a new operator that only returns a single copy of the service implementation:

Listing 5. Implementing a delegate using the Singleton pattern
public class OrderEntryDelegate {
    private static OrderEntryDelegate instance = 
        new OrderEntryDelegate();

    public static singleton() { return instance; }

    // Insert rest of the delegate code here including cached variables
}

But another problem with delegates using the new or singleton approach is that the class of the delegate is tightly coupled to the client code -- even though it is possible to change the implementation by replacing the JAR file in which it is packaged. Some teams get around this tight coupling problem by creating a pure Java interface which is returned by a class acting as a factory. This Factory class has a static method like the singleton class, but it returns an interface instead, as shown here:

Listing 6. Implementing a delegate using the Factory pattern
public interface OrderEntryDelegate {
    // Insert service signatures here
}

public class OrderEntryDelegateImpl
implements OrderEntryDelegate {
    // Insert delegate implementations here
}

public class OrderEntryFactory {
    private OrderEntryDelegate instance = 
        new OrderEntryDelegateImpl();

    public static OrderEntryDelegate getDelegate() {
        return instance;        
    }
}

Of course, this approach adds two more "Notes" to consider in the architecture. To eliminate one of the classes, some teams combine the factory with a default implementation by making it implement the delegate interface (very much like what is done with a JNDI InitialContext). And to get some reuse, some teams use the Delegate interface as part of the EJB local interface and bean implementation classes as shown here:

Listing 7. Reusing the delegate interface in a local session EJB
public interface OrderEntry 
extends OrderEntryDelegate, EJBLocalObject {
}

public class OrderEntryBean 
implements OrderEntryDelegate, SessionBean {
   // Insert service implementations here
}

Regardless, the implementation of the factory shown above is basically equivalent to a singleton, but it could have been built to take environment variables or other inputs. If this approach is chosen, then the issue of tight coupling shifts to the factory itself. In other words, the factory class is tightly coupled to the client code.

We have seen teams try and get around this by using a FactoryFinder pattern. In this pattern, each factory above provides an interface and implementation. The extreme version is that each extends a generic Factory interface -- which is really just a marker (so some simply use Object). The FactoryFinder is a one time only class that is used to bind the names to implementation classes:

Listing 8. Implementing a delegate using the Factory Finder pattern
public interface Factory {
}

public interface OrderEntryFactory extends Factory {
    public OrderEntryDelegate getDelegate();
}

public class OrderEntryFactoryImpl implements OrderEntryFactory {
    private OrderEntryDelegate instance = 
        new OrderEntryDelegateImpl();

    public OrderEntryDelegate getDelegate() {
        return instance;        
    }
}

public class FactoryFinder {
    private HashMap factories = new HashMap();
    public FactoryFinder() {
       // Bind the implementations, possibly using environment vars
    }
    public Factory getFactory(String name) {
        return (Factory)factories.get(name);
    }
}

Prior to the implementation of J2EE, this progression is how most teams' enterprise Java frameworks evolved. But it should be obvious that this extreme approach to delegates is a total reinvention of the EJB framework:

  • The FactoryFinder is equivalent to the JNDI Context.
  • The Factory is equivalent to a Home.
  • The Delegate is equivalent to a local EJB interface.
  • The DelegateImpl is equivalent to a local EJB implementation.

We seem stuck since the simplest extremes have shortcomings that minimize their effectiveness with respect to adaptability in a service oriented architecture.

Helper classes can be tested outside of the container and minimize the need for EJBs
But before we give up on the delegate approach, we should consider the benefits of using helper classes instead of an EJB to completely hide the use of J2EE from the application programmers (client or server) as desired by Cross. Most teams who use the helper pattern point out the benefit that they can be unit tested without an EJB container. When combined with the Factory- (or FactoryFinder-) based delegate pattern, the Helper class can be substituted for the DelegateImpl, for functional verfication testing (FVT, where multiple services are tested together). To enable this substitution, the Helper class only need extend the appropriate Delegate interface:

Listing 9. Reusing the delegate interface in a helper class
public class OrderEntryHelper
implements OrderEntryDelegate {
    // Insert service implementations here
}

The FVT Helper for the business object layer can simply include a hash table of instances that are loaded though static initializers or a constructor depending on the delegate style.

For system test and production, the session bean implementations are true facades that pass through to the helper class in every case:

Listing 10. Implementing a session bean using the Helper pattern
public class OrderEntryBean 
implements OrderEntryDelegate, SessionBean {
   private OrderEntryHelper helper = null;
   public void ejbCreate() {
       helper = new OrderEntryHelper();
   }

   // Insert service implementations here
   public CustomerData getOpenOrderForCustomer(int cID) {
       return helper.getOpenOrderForCustomer(cID);
   }
}

The SVT and production business object layer delegate can be replaced with a CMP implementation as shown here, or even a JDBC implementation (if you did not find the CMP discussion from the last two articles compelling). And in any event, the architecture can be set to only use a session bean implementations in the "outermost" delegates to minimize the use of EJBs but still get transactions, security, and even the distribution that they provide.

So how many "notes" does this leave us with? Actually even more than before, but they come in at different times:

  • One time only -- FactoryFinder, Factory interface
  • Across phases -- DelegateFactory interface, Delegate interface, Helper implementation, Key, Query and View DTOs
  • UT and FVT -- FVTFactory implementation (returns helper implementation if session/task services), CachingDelegate (for data services)
  • SVT and production -- SVTFactory implementation, Delegate implementation, EJB Home, EJB interface, EJB implementation.

So just when we thought the balance of benefits was tipping away from delegates, facades, and helpers, it seems to have swung back again.

The J2EE execution context can simplify the signatures and associated code
Or has it? One thing that many folks forget when hiding the J2EE framework behind delegates and helper classes is that the J2EE execution context is not available. In fact, many J2EE programmers forget to use it to simplify the service signatures.

For example, a J2EE client can get access to the user ID from the security context, eliminating the need to parse it from input parameters, and pass it into the service signatures as shown by this Servlet client, which illustrates how simple the code can be:

Listing 11. Invoking an unknown type of EJB component using Template Inheritance pattern
public class GetOpenOrderServlet extends CustomerServlet
{
    public void doGet(
        OrderEntryDelegate ref,
        HttpServletRequest req,
        HttpServletResponse res
    ){
        try {
    	    // The ref is passed in from the template superclass    
    	    CustomerData data = ref.getOpenOrder();

	    // Assume an include JSP method exists on the superclass
	    include("CustomerWithOpenOrder", data, req, res);
        }
        catch (OrderNotOpenException e) {
            // Occurs if the customer ID has no open order
            include("OrderNotOpenException", e, req, res);
        }
    }
}

The code shows that the ability to derive the customer ID from the J2EE context makes the signature of the service the same whether it is implemented on a customer centric session or entity EJB.

And finally, another point that most people forget about the J2EE specification is that it is simply a set of interfaces that your code implements as a way to mark various objects as certain kinds of components. It is just as easy to implement these components in a lightweight manner to provide a unit and function test environment as it is to implement a parallel framework such as that associated with a factory finder based delegate pattern.

However, this is cold comfort to those teams who would prefer not to build any frameworks at all and focus on the applications that support their business.

Where does this analysis leave us? On balance, and even though there is an unmet need for a lightweight unit test environment for EJB components, I have to agree with you that the architecture becomes simpler if you eliminate the separate delegate classes and simply directly invoke the appropriate EJB component.

Hope this helps,
Your EJB Advocate

divider


An unanswered question about CMPs without session facades

divider

Dear EJB Advocate,

The analysis was relatively helpful, but we are afraid that we took you off the track that we were really interested in exploring -- directly calling the Customer entity when using an object oriented approach. Our reasoning is that the Customer entity represents a "persistent" session with relationships to all the data required for the methods we need. And since we are using coarse grained stateless service oriented calls like you suggest, the session facade seems redundant.

Here are the three lines of code in question after the home reference was obtained (which we had cached as part of the client start up):

CustomerKey key = new CustomerKey(cID);
Customer ref = home.findByPrimaryKey(key);
CustomerData data = ref.getOpenOrder();

We initially liked the idea of deriving the customer ID from the J2EE context to simplify the signatures, but realized you have to be careful in situations like this where a customer service agent is involved -- their "persistent session" CMP still has to have a customer ID passed in, or you have to exploit a "current customer" CMR.

Anyway, we were surprised when a trace showed that there were two SQL calls issued -- one for the find and one for the getOpenOrder() method. There were two transactions, too. This result seemed to make sense once we thought about it and read the spec more closely. So we put a BMT around the entity using the template inheritance approach you showed to minimize the number of components we had to write and maintain. This approach fixed the problem of extra transactions and SQL. However, we were wondering if there was a better way.

Thanks,
Too Many Notes

divider


Custom entity Home methods may do the trick

divider

Too Many Notes,

Have you considered using custom entity EJB Home methods? This exchange is the first time that I have actually recommended them, because your team seems to be:

  1. Driven by minimizing the number of components, and
  2. Very comfortable using OO CMPs with CMRs.

Custom Home methods on entity EJB components are pretty easy to implement: just move the code from the session facade into a method called ejbHome<methodName> on the EJB implementation class:

Listing 12. Implementing an entity EJB home method
    public CustomerData  ejbHomeGetOpenOrderForCustomer(int id) 
    throws OrderNotOpenException
    {
	// Get the Customer DTO	
	CustomerKey key = CustomerKey new(id)
	Customer ref = findByPrimaryKey(key);
	return ref.getDataWithOpenOrder();
    }

then put the method in the EJB Home interface:

public CustomerData ejbHomeGetOpenOrderForCustomer(int id);

When using an entity Home method, the client code gets even simpler than for a session EJB, even if you don't cache the Home reference:

Listing 13. Invoking an entity EJB home method.
    InitialContext initCtx = new InitialContext();
    CustomerHome home = (CustomerHome)initCtx.lookup(   
        "java:comp/env/Customer"
    );
    CustomerData data = home.getOpenOrderForCustomer(cID);

The benefit of this code is that there is only a single transaction with or without wrappering with BMT code or a session facade. It works particularly well as an approach because of the correspondence between the entity and the user, and the existence of CMRs to the data needed to be accessed or updated as part of the "persistent session," as you called it.

In situations where the "relationships" between the data items needing to be composed for a unit of work are transient rather than persistent (in other words, the aggregation of data is ad hoc and not really supported by the underlying data model), it makes more sense to use a session facade.

Thanks for your notes. They helped me add other aspects of enterprise and service oriented architectures to consider.

OK then,
Your EJB Advocate

divider


Conclusion

To sum up the discussion, each type of EJB component has specific differences in:

  1. How the Home is retrieved from the JNDI context (the name used to find it and whether a narrow or cast is needed)?
  2. How the component reference is accessed from the Home (whether a key or query is needed or not, whether a create or a find method is used, and whether the service is directly accessible on the home itself)?
  3. What parameters are passed when invoking the service method (whether an ID parameter is passed or not)?
  4. What is the nature of the results returned (Disconnected, Connected)?
  5. What is the nature of the errors thrown (Checked, Unchecked)?

The best EJB components are service oriented, with functions that are:

  1. Coarse grained -- the functions are designed to minimize the number of calls between layers.
  2. Stateless -- all data associated with a given function is passed in and out on a single call, enabling any client request to be handled by any running instance in any order.
  3. Mediatable -- the data passed in and out is serializable such that it can be easily transformed.
  4. Adaptable -- the linkage between client and service implementation is logical (loosely coupled) rather than physical (tightly coupled). Loose coupling enables a different implementation to be substitiuted without changing the application, and enables the client and server to be distributed or co-located as needed.

POJO delegate classes are often used simplify invoking services in a common way. Helper classes are used to simplify building services. But delegates and helpers often evolve from simple to complex in order to provide adaptability as described above:

  1. Superclass methods -- no additional instance needed at run time.
  2. New operator -- creates an instance of the delegate implementation class.
  3. Singleton -- returns reference to the single instance of the delegate object.
  4. Factory -- a class that returns a delegate interface implementation.
  5. FactoryFinder -- a class that returns a Factory as described above.

The problem with this evolution is that delegates are redundant with the J2EE framework. Therefore, here are some features of FactoryFinder based delegates and helpers to consider in a tradeoff:

  1. Enable a pure Java programming model on both client and server.
  2. Enable UT and FVT without needing an EJB container.
  3. Enable SVT and production deployment without touching service implementations.
  4. Minimize the need for EJB components.
  5. Basically reinvent the Local Session EJB approach.

Likewise, here are some features of EJB components to consider:

  1. Exploiting the J2EE context can simplify the service signatures.
  2. Qualities of service are transparent to programmers.
  3. Deployment tools needed to generates deployment classes.
  4. Needs a runtime platform to realize implementation.
  5. Can implement a lightweight UT/FVT version of the J2EE framework almost as easily as a complete FactoryFinder delegate pattern.

Given that I am the EJB Advocate, you can imagine that I lean towards using J2EE components without separate delegate classes. It may be time for enterprise Java programmers to "unmask" them -- so here are some rules of thumb to consider when it comes to exposing an EJB component directly:

  1. If there is a natural "gateway" object that is closely tied to the user invoking the function, and if there are relationships supported in the underlying data model that give access to all the data needed for the function, then consider exposing the service as a custom home method on the entity exploiting CMRs. In short, the Home method becomes the equivalent of the session facade.
  2. If there are only arbitrary compositions of data to be read or updated driven from data in the service parameters with no "persistent" relationships between them, then consider exposing the service as a session bean method.
  3. However in case #2, consider looking for clusters of related data that can exploit case #1, to facilitate reuse.

Next, we will look at some application architectures from the "top down" and apply what we have been discussing over the last few months. We also hope to coordinate this article with a separate one on code generation tools that make top down development using EJBs really easy.

In any event, always remember that the EJB Advocate never says never or always.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Java technology
ArticleID=85702
ArticleTitle=The EJB Advocate: Is it ever best to use EJB components without facades in service oriented architectures?
publish-date=06152005