J2EE design decisions

Learn how to discern which design patterns and frameworks would work best for your enterprise applications

If we blindly used POJOs (plain-old Java objects) and lightweight frameworks, we would be repeating the mistake the enterprise Java community made with EJB (Enterprise JavaBeans). Every technology has both strengths and weaknesses, and it's important to know how to choose the most appropriate one for a given situation.

This book is about implementing enterprise applications using design patterns and lightweight frameworks. To enable you to use them effectively in your application, it provides a decision-making framework that consists of five key questions that must be answered when designing an application or implementing the business logic for an individual use-case. By consciously addressing each of these design issues and understanding the consequences of your decisions, you will vastly improve the quality of your application.

In this article you will get an overview of those five design decisions. I briefly describe each design decision's options as well as its respective benefits and drawbacks.

Business logic and database access decisions

There are two quite different ways to design an enterprise Java application. One option is to use the classic EJB 2 approach, which I will refer to as the heavyweight approach. When using the heavyweight approach, you use session beans and message-driven beans to implement the business logic. You use either DAOs (data access objects) or entity beans to access the business logic.

The other option is to use POJOs and lightweight frameworks, which I'll refer to as the POJO approach. When using the POJO approach, your business logic consists entirely of POJOs. You use a persistence framework (a.k.a. object/relational mapping framework) such as Hibernate or JDO (Java Data Objects) to access the database, and you use Spring AOP (aspect-oriented programming) to provide enterprise services such as transaction management and security.

EJB 3 somewhat blurs the distinction between the two approaches because it has embraced POJOs and some lightweight concepts. For example, entity beans are POJOs that can be run both inside and outside the EJB container. However, while session beans and message-driven beans are POJOs, they also have heavyweight behavior since they can only run inside the EJB container. So as you can see, EJB 3 has both heavyweight and POJO characteristics. EJB 3 entity beans are part of the lightweight approach, whereas session beans and message-driven beans are part of the heavyweight approach.

Choosing between the heavyweight approach and the POJO approach is one of the first of myriad design decisions that you must make during development. It's a decision that affects several aspects of the application, including business logic organization and the database access mechanism. To help decide between the two approaches, let's look at the architecture of a typical enterprise application, which is shown in Figure 1, and examine the kinds of decisions that must be made when developing it.

Figure 1. A typical application architecture and the key business logic and database access design decisions. Click on thumbnail to view full-sized image.

The application consists of the Web-based presentation tier, the business tier, and the persistence tier. The Web-based presentation tier handles HTTP requests and generates HTML for regular browser clients and XML, and other content for rich Internet clients, such as Ajax-based clients (asynchronous JavaScript and XML). The business tier, which is invoked by the presentation tier, implements the application's business logic. The persistence tier is used by the business tier to access external datasources such as databases and other applications.

The design of the presentation tier is outside the scope of this article, but let's look at the rest of the diagram. We need to decide the structure of the business tier and the interface that it exposes to the presentation tier and its other clients. We also need to decide how the persistence tier accesses databases, which is the main source of data for many applications. We must also decide how to handle concurrency in short transactions and long-running transactions. That adds up to five decisions that any designer/architect must make and that any developer must know in order to understand the big picture.

These decisions determine key characteristics of the design of the application's business and the persistence tiers. There are, of course, many other important decisions that you must make—such as how to handle transactions, security, and caching, and how to assemble the application—but answering those five questions often addresses these other issues as well.

Each of the five decisions shown in Figure 1 has multiple options. Each option has benefits and drawbacks that determine its applicability to a given situation. As you will see in this article, each one makes different trade-offs in terms of one or more areas, including functionality, ease of development, maintainability, and usability. Even though I'm a big fan of the POJO approach, it is important to know these benefits and drawbacks so that you can make the best choices for your application.

Let's now take a brief look at each decision and its options.

Decision 1: Organizing the business logic

These days a lot of attention is focused on the benefits and drawbacks of particular technologies. Although this is certainly very important, it is also essential to think about how your business logic is structured. It is quite easy to write code without giving much thought to how it is organized. For example, it is too easy to add yet more code to a session bean instead of carefully deciding which domain model class should be responsible for the new functionality. Ideally, however, you should consciously organize your business logic in the way that's the most appropriate for your application. After all, I'm sure you've experienced the frustration of having to maintain someone else's badly structured code.

The key decision you must make is whether to use an object-oriented approach or a procedural approach. This isn't a decision about technologies, but your choice of technologies can potentially constrain the organization of the business logic. Using EJB 2 firmly pushes you toward a procedural design, whereas POJOs and lightweight frameworks enable you to choose the best approach for your particular application. Let's examine the options.

Using a procedural design

While I am a strong advocate of the object-oriented approach, there are some situations where it is overkill, such as when you are developing simple business logic. Moreover, an object-oriented design is sometimes infeasible—for example, if you do not have a persistence framework to map your object model to the database. In such a situation, a better approach is to write procedural code and use what Martin Fowler calls the Transaction Script pattern (see Patterns of Enterprise Application Architecture). Rather than doing any object-oriented design, you simply write a method, which is called a transaction script, to handle each request from the presentation tier.

An important characteristic of this approach is that the classes that implement behavior are separate from those that store state. In an EJB 2 application, this typically means that your business logic will look similar to the design shown in Figure 2. This kind of design centralizes behavior in session beans or POJOs, which implement the transaction scripts and manipulate "dumb" data objects that have very little behavior. Because the behavior is concentrated in a few large classes, the code can be difficult to understand and maintain.

Figure 2. The structure of a procedural design: large transaction script classes and many small data objects

The design is highly procedural and relies on few of the capabilities of object-oriented programming (OOP) languages. This is the type of design you would create if you were writing the application in C or another non-OOP language. Nevertheless, you should not be ashamed to use a procedural design when it is appropriate.

Using an object-oriented design

The simplicity of the procedural approach can be quite seductive. You can just write code without having to carefully consider how to organize the classes. The problem is that if your business logic becomes complex, then you can end up with code that's a nightmare to maintain. Consequently, unless you are writing an extremely simple application, you should resist the temptation to write procedural code and instead develop an object-oriented design.

In an object-oriented design, the business logic consists of an object model, which is a network of relatively small classes. These classes typically correspond directly to concepts from the problem domain. As Figure 3 shows, in such a design some classes have only either state or behavior, but many contain both, which is the hallmark of a well-designed class.

Figure 3. The structure of a domain model: small classes that have state and behavior

An object-oriented design has many benefits, including improved maintainability and extensibility. You can implement a simple object model using EJB 2 entity beans, but to enjoy most of the benefits you must use POJOs and a lightweight persistence framework such as Hibernate and JDO. POJOs enable you to develop a rich domain model, which makes use of such features as inheritance and loopback calls. A lightweight persistence framework enables you to easily map the domain model to the database.

Another name for an object model is a domain model, and Fowler calls the object-oriented approach to developing business logic the Domain Model pattern.

Table Module pattern

I have always developed applications using the Domain Model and Transaction Script patterns. But I once heard rumors of an enterprise Java application that used a third approach, which is what Fowler calls the Table Module pattern. This pattern is more structured than the Transaction Script pattern, because for each database table, it defines a table module class that implements the code that operates on that table. But like the Transaction Script pattern, it separates state and behavior into separate classes because an instance of a table module class represents the entire database rather than individual rows. As a result, maintainability is a problem. Consequently, there is very little benefit to using the Table Module pattern.

Decision 2: Encapsulating the business logic

In the previous section, I covered how to organize the business logic. You must also decide what kind of interface the business logic should have. The business logic's interface consists of those types and methods that are callable by the presentation tier. An important consideration when designing the interface is how much of the business logic's implementation should be encapsulated and therefore not visible to the presentation tier. Encapsulation improves maintainability because, by hiding the business logic's implementation details, it can prevent changes to it affecting the presentation tier. The downside is that you must typically write more code to encapsulate the business logic.

You must also address other important issues, such as how to handle transactions, security, and remoting, since they are generally the responsibility of the business logic's interface code. The business tier's interface typically ensures that each call to the business tier executes in a transaction in order to preserve the consistency of the database. Similarly, it also verifies that the caller is authorized to invoke a business method. The business tier's interface is also responsible for handling some kinds of remote clients.

Let's consider the options.

EJB session façade

The classic J2EE approach is to encapsulate business logic with an EJB-based session façade. The EJB container provides transaction management, security, distributed transactions, and remote access. The façade also improves maintainability by encapsulating the business logic. The coarse-grained API can also improve performance by minimizing the number of calls that the presentation tier must make to the business tier. Fewer calls to the business tier reduce the number of database transactions and increase the opportunity to cache objects in memory. It also reduces the number of network round-trips if the presentation tier is accessing the business tier remotely. Figure 4 shows an example of an EJB-based session façade.

Figure 4. Encapsulating the business logic with an EJB session façade

In this design, the presentation tier, which may be remote, calls the façade. The EJB container intercepts the calls to the façade, verifies that the caller is authorized, and begins a transaction. The façade then calls the underlying objects that implement the business logic. After the façade returns, the EJB container commits or rolls back the transaction.

Unfortunately, using an EJB session façade has some significant drawbacks. For example, EJB session beans can only run in the EJB container, which slows development and testing. In addition, if you are using EJB 2, then developing and maintaining DTOs (data transfer objects), which are used to return data to the presentation tier, is tedious and time consuming.

POJO façade

1 2 3 Page 1
Page 1 of 3