DekGenius.com
Previous Section  < Day Day Up >  Next Section

3.3 Layering Your Architecture

This book is about building complex code in a simple way. The principle in this chapter, "Do one thing, and do it well," may seem like it argues against building complex software. But it simply means each major piece focuses on a single aspect of the overall solution.

You can organize an application in layers, so that you'll only have to deal with small, focused chunks of the application at any given point of time. In this section, I'll talk about the anatomy of a layer, the interfaces between layers, and the typical layers that you're likely to find in better Java applications. Before I start piling on generic information about how you should build layers, here's what you should not do:

  • Don't bite off too much in any given layer. Your layers should have a simple purpose, and they should be easy to digest at one sitting. If your layers are too fat, they'll be too hard to test, maintain, and understand.

  • Don't add a layer because you read about it in a book. In most cases, you should add new layers and design patterns based on real, experienced need, not assumed need.

  • If you're testing as you go, your tests will dictate some level of layering. Don't resist. Your tests mirror usage patterns in many ways, and they'll make your code easier to reuse and decouple as the need arises.

  • Pay attention to names. Think of each independent layer as a library of services, even if there's just one client at the moment. If you misname your services, your layer will be misused. If a name becomes a problem, change it. If it's too hard to change names, get a tool that lets you do so. Intellij's IDEA was used to build some of the software in this book, and its refactoring tools are extremely useful.

All developers layer; some just do so more effectively and intentionally. In the last section, you probably noticed that I threw some requirements out to handle later. I did so because the postponed requirements were natural layers for the emerging architecture. I explicitly defined layers for the business domain model and the data access object. I implicitly defined a layer for the user interface, a potential façade, and security. Let's look at the anatomy of a layer.

3.3.1 The Basics of Layering

OOP makes effective layering much easier. Each layer should do one fundamental thing. There are many different layers, but they only do a few different types of things:


Abstraction

Often, a software layer does what you need it to, but it may have an interface that's complex or awkward for your application. An abstraction layer presents a simpler interface for its clients. Façade layers are abstraction layers.


Application

These layers do the specific work of an application. The Account object is a functional application layer.


Service

These layers are similar to application layers, but they provide common services potentially needed by many applications. The line between a service layer and an application layer can be blurry.

3.3.2 Interfaces Between Layers

Figure 3-3 shows how the layers fit together. Ideally, you want a strict hierarchy. Typical layers are clients of layers below, and provide services for layers above. The lower-level layer usually exposes and maintains the interface, but that's not always the case. When you expose an interface (like the darker areas of Figure 3-3), you need to harden it more. Peer layers are at the same level of abstraction and do roughly the same type of work. Peer interfaces might have a tighter level of coupling than other layers, because peers often share bi-directional interfaces. Often, you might not want that tighter coupling.

Figure 3-3. A typical intermediate software layer has layers above and below
figs/bflJ_0303.gif


Think about the business model that we defined in our example above. The lower-level services that you use, those defined by the Java JDK, know nothing about our account. The account uses JDK library classes like String. If the interface of the JDK changes, then you've got to move Account to change with it. The Account, on the other hand, knows nothing about the DAO layer. The Account DAO layer that saves and loads an account is a client of the Account.

Most people think about interfaces as two equal operators, like two people shaking hands with presumably the same equipment. Usually, that's not the case. Most interfaces impose a direction. One layer presents an interface, and the others use it. Your interface should simultaneously provide complete services to the consuming layer while protecting the inner workings of the presenting layer.

In general, you'd like your layers to observe a strict hierarchy. Lower-level layers should not have direct knowledge of higher-level layers. Peer relationships can be especially troublesome, and deserve strict attention.

3.3.2.1 A word about good interfaces

You can make your layers much more effective if you concentrate on building a good interface around them. An interface bears some resemblance to food: everyone knows when it's good and when it's not, although not everyone will agree; it also takes whole lot more skill to make it right than to consume it. You could write a whole book on interfaces and still not say everything that needs to be said, but here are some guidelines for good interfaces. They should be:


Complete

You should be able to use your layer to do everything that you need to do through your interface. Everything doesn't need to be easy, but everything does need to be available.


Compact

Don't allow unnecessary duplication in your interface. When you duplicate, you're more prone to error, and you create additional maintenance burdens. Don't build in future requirements until you need them; don't add too many convenience methods; don't expose private methods.


Convenient

It should make it easy to do the things that you do the most. Convenience and compactness are often at odds. You'll want to assure compactness first, then convenience. That doesn't mean that you can completely blow off convenience.


Hardened

An interface should protect you from incorrect usage. If a null value breaks your code, throw the appropriate exception.


Consistent and predictable

An interface should observe standards, and should do similar tasks in similar ways.


Well-factored

Often, a few smaller, more focused interfaces are better than one all-encompassing interface.

Interfaces can take many forms, but all should observe these basic principles. They apply to messaging models, distributed remote procedure calls, and basic method invocations, too. The need for good programming hygiene is magnified between major levels of your architecture. Two excellent books for good interface design are The Pragmatic Programmer by Andrew Hunt and David Thomas and Effective Java by Joshua Bloch, both published by Addison-Wesley.

3.3.3 Common Layers

In my classes, many of my students ask me what layers a Java application should have. I tell them that it depends on what you want to do. Java application architectures are converging around a common architecture in several critical places. In truth, many of those layers are unnecessary, or mandated by ineffective architectures like EJB CMP. In other places, certain effective layers, in the right circumstances, are beginning to emerge.

3.3.3.1 Business domain models

Some development shops are moving away from a classic business domain model. Frankly, for some applications (like those built to baby-sit big relational databases), I'd have to agree with their direction. When you're doing nothing but viewing and entering relational data, a full object-oriented model is overkill.

When you do have a legitimate need for an effective business model, it should usually be the center of your application. Build services and interfaces around that layer to persist, display, notify, and manipulate that model. The model is too important, and often too complex, to clutter with other details—so transparency becomes extremely important.

3.3.3.2 Data access

Many strategies and frameworks exist to persist business objects. At the low end is a simple data access object implemented with JDBC. At higher levels, full persistence frameworks build in value-add services like lazy loading and caching across clusters. Since EJB CMP left such a bad taste in people's mouths for so long, Java developers are moving back toward simpler JDBC-based architectures, and also toward transparent persistence solutions such as JDO and Hibernate.

3.3.3.3 Communication

The era of CORBA, where a single business domain model was distributed across many different nodes, is dying rapidly. Instead, you're more likely to see strategic communication between hierarchical layers of an application (like session beans between an application server and a presentation server), and between major applications. As such, packaging a service with a technology like web services or JMS with XML is much more prevalent. Older systems come into play here; also, disparate technologies like Microsoft's .NET platforms are increasing in popularity.

3.3.3.4 Façades

A façade is often the primary client of your business model. Your goal is to provide a higher-level interface for the rest of the world. Before you add a façade layer, you must understand the value that it's providing. I've seen clients mirror the interface of their DAO layer, verbatim, within their façade. Façades are much more interesting when you're doing some consolidation, such as returning all members of a department across a distributed interface instead of making a separate round trip for each one.

For distributed architectures, you often want to present a simple, coarse-grained interface to clients, instead of a complex, complicated business domain model. The façade layer rolls many fine-grained methods up to a simpler interface.

If you're one of those developers who tend to believe everything written by a major software vendor, now would be a good time to pick up some rotten tomatoes and eggs. I'm going to get a little heretical. Your façade layer need not be distributed at all. You can simply deploy your presentation layer and your business layers on the same box. If your façade is not distributed, you probably don't need those session beans. And if you're not getting transactional integrity or security from your façade, you may not need a façade layer at all. You'll see more about the role of an effective façade in Chapter 4.

3.3.3.5 User interfaces

One of the most famous patterns for layering software is the model-view-controller architecture made popular by the Smalltalk community. Java model-view-controller architectures break user interfaces into three separate sections: a browser-based user interface, a controller, and a wrapper around the domain model.

Java developers fully embrace this concept and generally support an open source implementation called Struts to separate the user interface layers for a web-based application. Figure 3-4 shows the typical arrangement. A browser-based HTML interface calls a controller servlet, via HTTP. This servlet invokes an action, which wraps the domain model. Then, the controller calls a JSP, which compiles to a servlet, and returns a page to the client, possibly with results from the domain model.

Figure 3-4. The model-view-controller architecture provides an approach to layering user interface code (this figure is a slight variation of the original, and supports web user interfaces via Struts)
figs/bflJ_0304.gif


In the near future, JavaServer Faces may present a more attractive option than Struts, because it supports more sophisticated user interfaces. Still, I'm skeptical about JSF for a few reasons:

  • As with all event-based client-server models, JSF potentially increases communication costs between the client and server.

  • JSF has been designed in a closed committee. That's often been a recipe for disaster.

  • JSF is complex and will require specialized tools to be effective. The tools from some vendors tend to be proprietary, so it's important to watch the requirements evolve.

Starting with clean layers is only the first step. In the best of cases, you're going to want to refine your designs, and adjust your approaches. That's the subject of the next section.

    Previous Section  < Day Day Up >  Next Section