Skip to main content

skip to main content

developerWorks  >  Java technology  >

A taste of "Bitter Java": The Magic Servlet antipattern

developerWorks

Return to article

Perhaps the most common server-side Java antipattern is the Magic Servlet. This trap occurs when a programmer tries to do too much in a single servlet. Predictably, it grows unchecked, until even trivial maintenance is a nightmare. In college when I did my first serious programming, I had few organizational skills. My programs resembled an evil, amorphous blob. Over time, I developed tools and techniques, like object-oriented programming, that greatly enhanced my ability to organize and compartmentalize. As I moved over to server-side Java programming, I did not make the same mistakes with servlets, but I found that many of my customers did. I was shocked to find how many good programmers built servlets with little or no organization. At first, this behavior baffled me, but after pondering the reasons, I realized why.

You have probably learned that separating presentation and business logic is a good idea. Some refer to this technique as model-view separation. You probably also know that the roots of this separation go back to one of the earliest design patterns: Model-View-Controller. Client-server architectures have a logical separation point: the line between the client and server. Usually, you place the model on the server and the view on the client. Over time, many frameworks took the role of the controller, and model-view separation became trivial. Figure 1 shows the approach.


Figure 1. Model-view separation in a client-server application
Model-view separation

Problem: Servlets can hide view logic

When we try to apply this approach to the servlet model, it's not uncommon to make the assumption that the browser's HTML is the view, and it fires the model logic with an HTTP command to call a servlet. This assumption means that you don't need to worry about model-view separation at all, right? Wrong.

Let's consider a servlet that displays a post list for a message board (see Listing 1). You call the servlet with an HTTP GET. The organization is simple. The calling HTML code is the view, and the servlet is the model. Let's take a look at part of the program:


Listing 2. Model with hidden view

      ResultSet rs =
        stmt.executeQuery("SELECT subject, author, board" + 
	                        " from posts");

      // display the result set
      // rs.next() returns false when there are no more rows
      out.println("<h1>Message board posts</h1>");
      out.println("<TABLE border=\"1\">");
      out.println("<TD><b>subject</b></TD>\n");
      out.println("<TD><b>author</b></TD>\n");
      out.println("<TD><b>board</b></TD>\n");

If you haven't spotted the trap, notice that the query looks suspiciously like model logic reading from a database, and the information in the out.println statements looks like HTML. We are mixing model and view logic. How did this happen? The difference between this design and traditional client-server designs is that our solution is batch-oriented, requiring both an upstream view (calling the servlet) and a downstream view (displaying the result). Client-server programmers are used to working with a single view: we create the upstream view with pure HTML. But in this case, we created our downstream view with print statements in the servlet. Figure 2 shows the problem of the hidden downstream view.


Figure 2. Hidden view logic in the model
Hidden view logic

Now, the problem is crystal clear. Our servlet is serving as downstream view and model. Earlier interactive client-server programs did not have this problem. But in our case, because the servlet actually creates the downstream view, we need additional components to wrap our controller and model logic.

Refactored solution: Apply Model-View-Controller

The solution to this problem is to refactor with a modified model-view-controller that is more appropriate for servlet-based programming. We need to consider both the upstream and downstream view. Some will say that an explicit controller is not necessary. I prefer to use a controller, because I can use it to fire the model logic, then do some validation processing, and then route the user to an error-handling or result page as appropriate. Figure 3 shows our refactored architecture.


Figure 3. Refactored solution
Refactored solution

The following programs will help us look at each piece in more detail. (Keep in mind that these programs are from Bitter Java, and they still need quite a bit more refactoring. For example, we do not connection pool and do not use a common command interface.)

  • The servlet serves as our controller (see Listing 3). It is like a traffic cop. The upstream view invokes the servlet controller via HTTP. The servlet's responsibility is to execute any business logic, and based on the outcome, pass the results to the appropriate downstream view, a JSP page page.

  • The Java command is our model logic (see Listing 4). Think of commands as a thin wrapper around business logic. The simple structure of commands makes them very easy to build into tools, like WebSphere. It's also easy to automate command generation with something like XML translations. The command API is simple: you use methods to create them, set any input parameters, execute them, get the results, and destroy them.

  • The JSP page is our downstream view (see Listing 5). The JSP page is a template that looks like HTML, with inline Java code and placeholders for beans that host dynamic content. The servlet passes the result of a command to the JSP page in the form of a bean. The JSP container then compiles the JSP into a servlet and executes it, to create HTML. The application server then ships the HTML back down to the client.

We benefit tremendously with this approach for many reasons:

  • The command layer is easy to automate. In fact, WebSphere wizards and VisualAge for Java both use similar approaches. If you're doing command-line development, you can easily create stubs for commands.

  • Model-view-controller insulates the user interface from model changes, and vice-versa, making maintenance much simpler.

  • The approach insulates specialties, so that designers and programmers can use familiar tools and languages.

Programmers have simplified their code structure and eased maintenance tasks with Model-View-Controller for decades. If you're already building code this way, you can better recognize and refactor bad implementations. You need not invent your own, as I've done here. You can use WebSphere's wizards to create code that corresponds roughly to this programming model. If you'd prefer an open model, check out Jakarta's Struts framework. You can also replace the command layer with session EJB components. Whatever you decide, make sure that you achieve a clean separation between your model and view to stay out of the most fundamental server-side antipattern: the Magic Servlet.

If you'd like to read more about this programming model, see Resources for a relatively old, but very useful IBM redbook called Design and Implement Servlets, JSPs, and EJBs for WebSphere, which details the whole architecture nicely. You can also check out my book, Bitter Java, which contains two full chapters on the Magic Servlet and related antipatterns.

Return to article