Monday, 14 September 2009 14:40

Java Enterprise Design

Rate this item
(0 votes)
1.1 Design Patterns

A design pattern is a recurring solution to a recurring problem. From a programming perspective, a pattern provides a set of specific interactions that can be applied to generic objects to solve a known problem. Good patterns strike a balance between the size of the problem they solve and the specificity with which they address the problem.

The simplest patterns may be summed up in no more than a sentence or two. Using a database to store information for a web site is a pattern, albeit a fairly high-level and obvious one. More complex patterns require more explanation, perhaps including the use of modeling languages or a variety of other forms of longer description.

Design patterns originated outside the computer industry, originally showing up in conventional (as opposed to computer systems) architecture. Architects of buildings and architects of software have more in common than one might initially think. Both professions require attention to detail, and each practitioner will see their work collapse around them if they make too many mistakes.

The book to read, if you're interested in the architectural origins of design patterns, is A Pattern Language: Towns, Buildings, Construction by Christopher Alexander (Oxford University Press). Widespread acceptance of design patterns in software began with the publication of Design Patterns: Elements of Reusable Object Oriented Software (Addison-Wesley), by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, also known as the "Gang of Four."
Design patterns are discovered as much as created. Some of the most effective patterns emerged from two decades of object-oriented (OO) design theory and the fewer but highly intensive years of enterprise Java. In many cases, the challenge has been to move from best practices and gut feelings to clear, defined sets of activities that can form the basis for communication between developers. It's amazing how much easier it is to suggest that someone use an Actor-Observer pattern (which allows one piece of code to register interest in the activities of another and receive notifications of key events) than to explain how to implement an event model.

We hope that readers will have two reactions as they explore the patterns in this book. The first is to say, "That's cool" (or, if the boss is around, "That's useful!"). The second is to say, "So, what's the big deal?" Both reactions mean that we're doing our job—describing approaches that work. Design patterns in enterprise applications can be surprisingly simple on the surface. Of course, it's that generic aspect that makes them effective patterns in the first place.

Any developer who has built anything with J2EE, or even with significant components of J2EE, has probably discovered at least one or two of these patterns already. In fact, some of the patterns in this book originated before J2EE and influenced its development, and they can also be applied in other languages and environments. Using strong patterns makes for a better programmer, designer, or architect, regardless of the underlying language.

1.1.1 The Anatomy of a Pattern

Software design patterns represent a small fraction of the many types of patterns in the world. We mentioned already that design patterns started in the world of architecture. There are also business patterns, networking patterns, and even social patterns that describe how people should interact. So what makes a pattern a pattern?

To describe a general pattern, we'll take an example from city planning. Building a city is a lot like building a complicated application. There are buildings (objects) that encapsulate certain functions (police, fire, merchant banking, baseball stadiums, etc.). These buildings are largely independent, interacting only via the goods, services, and people (messages) that flow in and out of them on roads (interfaces). In a city, a design pattern might be "building in blocks."

There are several ways of describing a pattern, but they all have several aspects in common. The first part of the "building in blocks" pattern description is its name. The name is important because it is a simple way for city planners to uniquely refer to the pattern. Any time one planner refers to "building in blocks," all the other planners should know what he is talking about. If the pattern has an alternate name—for instance, "grid layout"—it should be mentioned also.
The second part of the pattern description is the goal. This describes the problem solved by the pattern, or what we hope to achieve by implementing the pattern. The goal is sometimes described in terms of the forces that apply to the pattern. One of the forces acting on the "building in blocks" pattern is that it is difficult to navigate in a large city where the roads do not run parallel. Another is that buildings are easier to build when they are square, a process that requires perpendicular intersections.

The most important aspect of a pattern description is the description itself. This element tells how the pattern works. It is usually phrased in terms of participants (the objects that interact in the pattern). The description of a pattern summarizes the characteristics of the participants, as well as how they interact. Note that the participants must be generic enough that the pattern can be reused, but specific enough to solve a particular problem: a balance that changes depending on the goal of the pattern.

For "building in blocks," we would likely define buildings, streets, avenues, vehicles, and people as participants. People and vehicles interact by navigating between buildings on the streets, which are laid out in a grid. However, we can also treat this pattern as something more generic. Rather than describing buildings, pedestrians, streets, and so forth, we can talk about components (buildings) connected by pathways (streets) and navigated by travelers (people and cars). This allows us to apply the same pattern to the layout of an office (cubes, hallways, workers), or of a restaurant (tables, aisles, waiters), as well as the layout of a city.

Pattern descriptions typically have an implementation section. Implementation covers any specific information that goes into making a pattern work in practice. In software patterns, the implementation section is where you find actual code—or, at least, code fragments. The code is a sample implementation that demonstrates the central ideas of the pattern. For the "building in blocks" pattern, we might mention in the implementation section that streets are often named by numbers, while avenues are usually referred to by name. Since this pattern doesn't address the variety or ratios of building types a city might require, we wouldn't emphasize how many police stations and convention centers the city might need. That would be a different pattern.

Finally, it is often helpful to show cases where the pattern has been applied successfully. Not only does this information help readers decide if a pattern is applicable to their problem, it also provides a reference for finding more detailed information. New York City, Washington, D.C., and Philadelphia all use the "building in blocks" pattern. Most of Boston does not, and as a result requires the largest public works project in history to add more capacity to the roads.[1]
 See http://www.bigdig.com/ for a detailed look at Boston's traffic woes and the world's largest refactoring activity.

1.1.2 Presenting Patterns

In this book, we take a somewhat unorthodox approach to working with design patterns. There are several books available, including the Gang of Four's, that present lists of patterns, sometimes organized according to a guiding principle or two. These are generally referred to as pattern catalogs. Rather than present another pattern catalog, we're going to discuss different aspects of J2EE development and introduce the patterns in context. The goal is to give the reader a set of patterns that build on each other. By presenting the patterns in the larger context of J2EE applications, we hope to foster a more complete understanding of effective Java Enterprise architecture.

For reference purposes, Appendix A contains an abbreviated form of a conventional pattern catalog, providing quick overviews of the problems dealt with by each pattern discussed in the book, and references back to the more detailed discussion.

1.2 J2EE

This book expects that you know the fundamental Java language and have some basic familiarity with the J2EE APIs. A J2EE environment differs from a Java 2, Standard Edition (J2SE) environment in that it offers a wider range of services than a standalone application could expect to find. J2SE is geared towards providing core language features (I/O, text manipulation, variables, object semantics), standard utility classes that apply in a variety of settings (collections, mathematics), and features required for building client applications (GUIs, and some basic enterprise integration, including access to databases and naming services).

The J2EE application model is built on a division of labor into various tiers. The client presentation in a web browser, applet, or Java application is separated from server side logic in a JavaServer Page or Java Servlet and the business logic in a database or Enterprise JavaBeans. The J2EE APIs are focused on implementing the interactions between tiers. The interfaces to each tier are standardized, allowing programmers with an understanding of the core J2EE concepts to easily apply their skills to any J2EE-based project.

The core of a J2EE application deployment is a J2EE-compliant application server. The application server supports hosting business logic components and web components, providing access to the full range of J2EE capabilities. Note that the J2EE API doesn't say very much beyond the essentials about how these servers should be designed, or how deployment, maintenance, and general administration should be conducted. The focus of the J2EE API, instead, is on programmatic interfaces and runtime behavior. This specialization can make it difficult to transfer administration skills from, say, IBM WebSphere to BEA WebLogic. Code, however, should transfer transparently. Figure 1-1 shows the canonical J2EE application model.

 
Figure 1-1. Canonical J2EE application model

Each J2EE API itself is simply a wrapper for a service provided by the J2EE container or by an external component within the enterprise. A full-scale J2EE environment could include one or more J2EE application servers hosting servlets and Enterprise JavaBeans, as well as an external transaction manager, an Oracle or DB2 relational database, and a messaging middleware system. Full J2EE platforms implement the following APIs:

Java Servlet API
Allows a web server to process HTTP requests and return content to a client. The servlet code can make use of any other J2EE service in the process of fulfilling the request.

Java Server Pages
These text files include fragments of Java code and are stored on a web server. A typical JSP consists of HTML with embedded Java code to customize the display according to the application's requirements. When a client requests a JSP, the server compiles the JSP into a Java Servlet and, if it has not already done so, executes the JSP and returns the results. Since JSPs are easier to write and modify than a Servlet, they are often used to create the HTML user interface for a web-based application.

Enterprise JavaBeans
Allows the creation of server-managed distributed objects, representing the business data and logic behind an application. EJBs are divided into three categories: entity beans define the data structure for the application; session beans provide business methods that can manipulate entity beans and other resources; message-driven beans are similar to session beans, but they are triggered by the arrival of a message from a messaging middleware system. The EJB standard also includes full support for transactions. Managing EJBs is one of the main duties of a J2EE server.

JNDI
(Java Naming and Directory Interface)
Provides generic support for accessing directory services. In addition to providing a frontend to enterprise directory servers, such as LDAP-based employee directories, JNDI is used within the context of a J2EE application to identify resources such as Enterprise JavaBeans and database connections. By accessing all external resources via JNDI lookups, an application can be easily relocated or distributed.

JavaMail
API
Enables support for generic point-to-point electronic messages. For all practical purposes, this means Internet email. JavaMail uses a generic architecture to support pluggable message transport and retrieval systems. The default installation from Sun includes support for SMTP, IMAP, and POP, and support for other protocols is available from a variety of third parties. While J2EE environments must provide an implementation of JavaMail, they do not need to provide any of the infrastructure for a particular transport mechanism.

Java Message Service
API
Allows J2EE applications to integrate with Message Oriented Middleware (MOM) packages, such as Sonic Software's SonicMQ or IBM MQSeries. MOM packages allow messages to be routed between applications, providing delivery and quality of service guarantees that simple email cannot provide.

Java Transaction API
Allows applications to manage their own transactions. The Java Transaction API allows multiple components on different tiers and different servers to participate in a single, distributed transaction.

The J2EE standard also has a few additional features and requirements. It mandates interoperability with CORBA-based distributed objects. In addition, it requires support for the full J2SE API set, which provides the JDBC API for database access. Finally, it provides a standard, XML-based system of deployment descriptors, which describe the structure of a J2EE application (particularly EJB components and web applications), enabling faster deployment.

For tutorials and reference information on the individual APIs that make up J2EE, we suggest (with some admitted bias) Java Enterprise in a Nutshell, by Jim Farley, William Crawford, and David Flanagan. There are also a variety of books in the O'Reilly Java Series (and elsewhere) that do an excellent job of explaining each API in much more detail than we could do here.

You don't need to use every last J2EE API to build a J2EE application. While there is occasionally some confusion, an application is technically "J2EE" if it uses even one of the J2EE APIs. It's common to associate J2EE with Enterprise JavaBeans. This is particularly the case because EJBs, despite the name, are probably the most conceptually difficult J2EE component for programmers who started out with J2SE.

We can say that applications that use the full J2EE application model from HTML presentation through to an EJB-based business layer are "J2EE applications," while others simply "use J2EE technology," but the difference isn't something to waste much time on. The important thing is that you can use the mix of J2EE technologies appropriate for your application. Later in the book, we'll describe patterns geared toward effectively balancing the J2EE application model and real-world requirements.

 1.3 Application Tiers

We've gotten all this way without defining enterprise applications. This is because they often defy simple definition. Enterprise applications range from mainframe-based transaction processing applications with aging green-screen terminals to phone systems, from traditional client/server to intranet web sites, and even to Amazon.com.

All enterprise applications are divided into tiers. These tiers are sometimes referred to as components, although this term is a little misleading; more than one component is frequently present on any given tier. A tier can be thought of as a collection of software with a particular scope of operation and a defined set of interfaces to the outside world.

Different people divide enterprise applications in different ways. The official J2EE application model, as discussed above, divides an application into a Client Presentation tier, a Server Side Presentation tier, and a Server Side Business Logic tier. Enterprise information systems, such as databases, constitute a fourth tier.

We call this the "official" model because it's what Sun's J2EE documentation proposes, but in reality, there's a lot of scope for individual choice. For example, when thinking about application tiers for this book, we came up with five layers:
Client Presentation tier

Provides a user interface to the end user. The client can be "thin" (typically a web browser), "fat" (a full scale application), or something in between.
Server Side Presentation tier
Provides the Client Presentation tier with the materials, such as HTML, required to produce the user interface.

Server Side Business Logic tier
Includes the business methods sitting behind an application, performing actions like registering customers, taking orders, and managing shipping. These can often be thought of as the "verbs" of an application.

Server Side domain model
Includes the data model used by the business logic tier, or the "nouns" of the application. The domain model includes entities like customers, orders, stock records, and any other information that is manipulated in the course of the application's life.

Enterprise Integration tier
Connects the application to other systems. This connection can take place via CORBA, web services, Message Oriented Middleware, email, or even sneakernet.

Notice that we've called two of these tiers "presentation" tiers. That's because both are responsible for creating the interface ultimately presented to the client. Different technologies are used to implement the client-side and server-side presentation tiers (HTML and JavaScript on the client side, Servlets, JSPs, and so forth on the server), but both are complementary.

These five tiers are certainly not canonical. Many applications don't include an Enterprise Integration tier, and many writers correspondingly don't treat it as a tier at all. The business logic and domain tiers are often combined. The persistence engine for the domain tier, typically a relational database, is often considered a separate tier because it is housed separately. We didn't list it that way because, in a perfectly compartmentalized application, the domain tier is the only part of the application that accesses the database layer.[2]

This particular distinction is where a lot of people tend to play fast and loose, and often for good reason, since the domain tier can be most severe performance bottleneck in a large application. Much of Chapter 7 is devoted to addressing this issue. The database can also be seen as part of the Enterprise Integration tier, particularly if it plays host to a large quantity of application logic as well as raw data.

If we assume the client to be the "bottom tier" and the enterprise layer to be the "top tier," we can go on to say that tiers generally see higher tiers but not lower tiers. Furthermore, they should see as few other tiers as possible, using interfaces that are as tightly defined as possible. This makes it easier to modify one tier without affecting others. There are always exceptions to the order listed above. For instance, a fat client might skip the Server Side Presentation tier and interact directly with the business objects on the Server Side Business Logic tier via Enterprise JavaBeans. The same application may include a web interface where a Server Side Presentation tier component uses the same EJBs to create HTML.

1.3.1 Component-Based Development

We just mentioned that J2EE tiers are sometimes referred to, in error, as components. We shouldn't be too harsh, though, because it's a logical assumption to make and can even sometimes be true. A component is a self-contained piece of software with a known, well-defined interface that can be used by other pieces of software. According to this definition, a J2EE tier is a component, but it's a component that can be subdivided (at least hopefully!) into a number of subsidiary components, which in turn can be rearranged to modify the functionality of the software itself.
1.3.1.1 Characteristics of components

If areas of the program depend closely on each other, we say the components are tightly coupled. When one component is modified, its partner will usually need to be modified to match, and vice versa. For this reason, effectively sharing tightly coupled components can be a real challenge. When multiple developers work on these components, each developer risks breaking the entire application and the work of other developers. In the enterprise world, the situation becomes even murkier: individual applications can be components of large business process flows, and changing one small section can, in the worst possible case, cause entirely different applications to fail!

A component is specialized when it can only perform a specific task. While there is nothing inherently wrong with specialization, components that are specialized for a certain task are difficult to extend or reuse. To perform new tasks, the entire component must be copied before it can be changed. This divergence will cause maintenance headaches in the future, since updates will need to be repeated in several places.

Designing for change up-front can help solve these problems. Defining clear interfaces between components separates their interactions from individual implementations. As long as a component preserves its interfaces, it can change internally without breaking its partners. A component can even be replaced with an entirely different one, as long as the interfaces are the same. When components work together using these interfaces, they are said to be loosely coupled. Loosely coupled components can vary their implementation independently, which makes each part more likely to be reusable and easier to share.

To reduce specialization, larger components must be broken up into a collection of smaller ones. Code that is specific to a particular task should be separated out into a subcomponent, which encapsulates the task's functionality. This subcomponent can be modified and replaced independent of the rest of the application, promoting sharing and reuse.

In some ways, designing for extensibility may seem like a contradiction: expecting the unexpected. In fact, as a developer, you usually have some idea about where the software might evolve. This is often the case during iterative software development—you build a subset of the full product in each iteration, knowing in advance what features will be added by the time it is released. You can use this intuitive knowledge as a basis for making your software more extensible.

As software grows, however, it will probably be used in ways you never expected. Users will create new requirements on the program. If your design was not extensible, you will need to complicate it to meet these demands. Eventually, you will realize which areas of the program should have been extensible to begin with and rewrite large amounts of the program in order to retrofit them. The process of adding extensibility into an existing application is known as refactoring.

1.4 Core Development Concepts

When evaluating the end product of any enterprise development project, we can score it on four factors: Extensibility, Scalability, Reliability, and Timeliness. Different projects emphasize these factors to different degrees: NASA
programmers will emphasize reliability above all else, giving appropriately short shrift to timeliness concerns. A startup may emphasize scalability and timeliness, with concerns over extensibility put off for the next release.[3]

Of course, there are other considerations that come into play on individual projects, such as manageability and usability, but we've found this set to be appropriate for our purposes.
Obviously, each of the four issues affects the others at some level. A highly extensible system might be made more scalable by plugging in higher performance components, and time spent up front building support for scalability will pay off in timely deployment of later versions. The important thing to know is that design patterns can improve performance in all four areas. In this book, we focus on extensibility and scalability in particular.

1.4.1 Extensibility

The one constant in software development is that requirements always change. With each version of a product, there are bugs to fix and ideas to develop into new features. These days, particularly in business programming, requirements often change between the time a product is designed and the time it is released. When requirements do change, software divides into two categories: the kind that can be easily extended and the kind that can't. Unfortunately, determining in advance which category your program fits into is difficult. If you were trying to extend a toaster, it might be easier to add the ability to toast bagels than bake cakes.

In general, the extensibility of software determines how easily changes can be accommodated. It is easy to say whether the program was extensible or not in hindsight, but for the program to be really useful, we must have a sense beforehand. In a first version, it is sufficient to show that a program avoids the most common extensibility pitfalls. Once a few versions have been released, empirical evidence provides a much clearer picture.

There are a few common reasons that programs are hard to change. One that occurs most frequently is the fear of breaking supposedly unrelated code. We've all been in the situation where a seemingly innocuous change to one object has caused another, seemingly unrelated part of the program to suddenly stop working. This kind of problem is usually discovered weeks later, and you get stuck revisiting several weeks worth of changes to find the single, obscure dependency you broke.

As an application grows, the number of dependencies tends to go up, and consequently the amount of testing necessary after each change. Eventually, the fear of changing anything outweighs the benefits of new features, and development grinds to a halt. While this scenario sounds extreme, it is familiar to anyone who has worked on a large legacy application. In the enterprise environment the situation can get even worse, as changes to one application can affect entirely separate—but nonetheless related—systems. Imagine a financial system that starts requiring all transactions in Euros: if the purchasing computers aren't switched over at the same moment, chaos will ensue.

Even if you know all the dependencies, it can still be hard to change code. Changing one object's interface means not only changing that object, but also updating all the objects that rely on the interface. While providing backward-compatible interfaces is often a good compromise, this too becomes impossible after a few generations. In the end, someone gets stuck updating all the dependent classes—a tedious, thankless task, to say the least.

In short, be careful with dependencies. While objects must interact with each other in order for a program to do anything, the number of dependencies should be limited. In an extensible program, interfaces are kept as tight as possible to make sure objects only interact in well-known ways. Of course, there is a tradeoff here, as always. Components with rich interfaces can be more useful than components without them, but by their very nature, richer interfaces create tighter dependencies with other components.

Using clear interfaces separates an object's implementation from its interactions. This makes the specific implementation independent of the rest of the application, allowing implementations to be fixed, changed, or replaced at will. The application is no longer a single piece, but a collection of semi-independent components. Multiple developers can work on components without breaking, or even knowing about, the larger application. Components provide a certain functionality, which can be tested on its own and used in multiple applications. Multiple components can also be grouped into a larger entity that can itself vary and be reused. By using these larger components, an application can gain functionality without getting more complicated or sprouting more dependencies. In many ways, extensibility is simply a measure of how easy it is to understand the code. If the division of labor among components and the interfaces between them are clear, software is easy to extend. It can be worked on independently by a number of people without fear of interference, and new functionality does not further complicate the program.

Design patterns can help solve many extensibility problems. By documenting a particular aspect of architecture, a design pattern makes it easier to understand. Design patterns can be communicated to other developers, minimizing the risk that someone who does not fully understand the architecture will break other parts of the application. Most importantly, even the act of evaluating design patterns—whether they turn out to be applicable or not—forces developers to think out and articulate their design up front, often exposing flaws in the process.
1.4.1.1 Techniques for extensibility

Successful applications are constantly enhanced and extended, but these updates can come at substantial cost. Small design flaws in the original program are quickly magnified. All too often, the fix to one problem creates yet another problem that requires a new fix. These layered fixes make the code unwieldy and reduce opportunities for reuse. As more and more special cases are added, exhaustive testing becomes nearly impossible. Eventually, even adding a simple new feature becomes prohibitively expensive.

To make life even more complicated, these overgrown programs become increasingly difficult to understand. If learning one area of a program relies on learning five others first, it's unlikely that developers will be able to learn it fast. One person can reasonably build a text editor; however, he would have to be very dedicated to add a spellchecker, web browser, and other features. A clean, consistent base architecture allows many developers to contribute to the application.
The hallmark of extensible software is that it is designed to change. Whether you are working on an initial design or refactoring an existing one, there are several generic techniques that can make your designs more extensible:

Decoupling
We have already talked a little bit about loose and tight coupling. In a loosely coupled system, components can vary independently. They can be prototyped, updated and replaced without affecting other components. Because of this, loosely coupled systems will generally be more extensible.

Centralizing
When functionality is spread out over multiple components, making simple changes may require updating many parts of the code. It also makes the code harder to follow and therefore harder to understand and share. By gathering common functionality into central resources, the application becomes easier to understand, update, and extend.
Reusing
Adding too much functionality to a single component specializes it. A specialized component cannot easily be adapted to perform other functions, so code must be duplicated. This duplication makes the code harder to maintain and update. A design in which common functionality is encapsulated in reusable components is more extensible, because larger components can be composed effectively from specialized existing components. Design patterns may well have their most profound impact in the area of extensible systems. We'll use all three of these approaches in the chapters ahead.

1.4.2 Scalability

When building desktop applications, you generally have the ability to define your platform, at least in broad terms. A word processor may have to deal with documents from 1-1000 pages long, but it won't have to deal with 1-1000 users editing the document at the same time. Assuming your test lab is outfitted realistically, it is possible to determine whether an application will perform appropriately. With one user, a program only needs to perform one task, or at most two or three related tasks, at a time. The time it takes to perform each task is a measure of the application's performance. Performance depends on the task itself and the speed of the underlying hardware, but that's about all.

Enterprise applications aren't that simple. The time it takes for a web server to process a request certainly depends on the performance of the server application and hardware, but it also depends on how many other requests are being processed at the same time on that server. If the application involves computing by multiple servers, the web server's speed will also depend on the speed of those other servers, how busy they are, and the network delays between them. Worst of all, the speed of transmitting the request and response—which is based on the speed of all the networks in between the user and the server—factors into the user's perception of how fast a transaction was processed.

While developers generally can't control network speed, there are things they can control. They can modify how quickly an application responds to a single request, increasing its performance. They can also vary how many requests a server can handle at the same time—a measure of the application's scalability.

Scalability and performance are intimately related, but they are not the same thing. By increasing the performance of an application, each request takes less time to process. Making each transaction shorter would seem to imply that more transactions could be performed in the same fixed time, meaning scalability has increased. This isn't always the case. Increasing the amount of memory used to process each request can increase performance by allowing the server to cache frequently used information, but it will limit scalability, since each request uses a greater percentage of the server's total memory; above a certain point, requests have to be queued, or the server must resort to virtual memory, decreasing both scalability and performance.

Scalability can be broadly defined as the ability of an application to maintain performance as the number of requests increases. The best possible case is a constant response time, where the time it takes to process a request stays the same regardless of the load on the server. Ideally, an enterprise application will be able to maintain a more or less constant response time as the number of clients reaches the standard load for the application. If a web site needs to serve 200 requests a second, it should be able to serve any one of those 200 requests in the same amount of time as any other request. Furthermore, that amount of time needs to be reasonable, given the nature of the application. Keeping the user waiting more than a half second for a page is generally not acceptable.

Linear scalability
is when the time it takes to process n requests is equal to n times the time to process one request. So if one user gets a response in 1 second, 10 simultaneous users will each have to wait 10 seconds, as each second of processing time is divided 10 ways. Enterprise applications may hit linear scalability when under a particularly heavy load (such as after they have been linked to by http://www.slashdot.org). If the load on the server goes up to 400 users a second, the time required for each response might double from the 200 user level.

At some point, a program reaches its scalability limit, the maximum number of clients it can support. An application's scalability is usually a combination of all three factors: constant response time up to a certain number of clients (ideally to the maximum number of users the application needs to serve), followed by linear scalability degrading until the scalability limit is reached. Figure 1-2 shows a graph of performance versus number of clients, which is how scalability is usually represented.
 
Figure 1-2. A graph of scalability
 
Building scalable systems almost inevitably involves a trade-off with extensibility. Sometimes breaking a larger component into smaller components, each of which can be replicated multiple times as needed, increases scalability. But more often, the overhead of communicating between components limits scalability, as does the increased number of objects floating around the system.

The design patterns in this book often focus on the interactions between application tiers. These interactions are where most scalability problems initially appear. Using effective practices to link these tiers can overcome many of the performance debts incurred by separating the tiers in the first place. It's not quite the best of both worlds, but it is usually a good start.

Of course, most systems do not need to be designed for unlimited scalability. In many cases—particularly when developing systems for use within a defined group of users (the case for most intranet applications)—only a certain number of clients need to be supported, and the trade-off between scalability and extensibility tilts toward the latter.

Design patterns support scalability in a number of ways, but primarily by providing a set of approaches to allow resources to be used efficiently, so that servicing n clients doesn't require n sets of resources. In addition, patterns can enable extensibility, and extensible systems can often use that extensibility to improve scalability by distributing operations across multiple servers, plugging in higher performance components, and even by making it easier to move an application to a more powerful server.

1.4.3 Reliability

Reliable software performs as expected, all the time. The user can access the software, the same inputs produce the same outputs, and the outputs are created in accordance with the software's stated purpose. Needless to say, complete requirements gathering is vital to ensuring software reliability, since without clear requirements there is no way to define what correct behavior actually involves. Requirements gathering is also important in figuring out what constitutes a reliable system: does the system need to stay up 99.999% of the time, or can it be taken down for maintenance for 2 hours a night?

Similar to scalability, a reliable system depends on the predictability of its underlying components. From a user's point of view, reliability is judged for the entire system, including hardware, software, and network elements. If a single component malfunctions and the user cannot access the application or it does not work correctly, the entire system is unreliable.

Corporate software projects are subject to specific quality requirements, and reliability is usually first among these. Most larger software teams have one or more people in a Quality Assurance role, or they make use of the services of a dedicated QA team or department. Larger projects, particularly in regulated industries such as health care, are subject to software validation processes and audits. A software audit can include every aspect of the development cycle, from initial requirements gathering through design, up to final testing and release procedures.

Design patterns can play a major role in ensuring reliability. Most of the patterns in this book are acknowledged, at least by some, as best practices within the industry. All of them have been applied countless times in enterprise application development projects. Design patterns can be validated at a high level and incorporated early on in the design process. This kind of planning makes the final validation process simpler, and generally produces code that is easier to audit in the first place.

1.4.4 Timeliness

The final goal of any software development project is a timely delivery of the finished software to the end users. At least, that's the way the end users generally see it! Design patterns might have less impact in this area than in the other three, although having a catalog of proven solutions to standard development issues can be a timesaver during the implementation phase.

The real time savings tend to come in subsequent release cycles and in projects that rely on an iterative development methodology. Since most patterns involve some sort of modular design, applications that use them will be easier to extend in the future, providing timeliness advantages to the next generation of software as well. Programmers can understand the structure of the application more easily, and the structure lends itself more readily to change: Version 2.0 can be made available much more readily than would otherwise be possible.

It is possible, of course, for patterns to negatively affect a project schedule. Solving a problem by writing code that conforms to a generic design pattern may take more time than solving the problem in a more direct fashion, although this investment is often recouped in later phases of the development process. But complex patterns can also introduce complexity where none is required. For enterprise applications, though, the balance tilts towards the patterns approach. Major systems are rarely, if ever, designed and implemented as one-offs: the ROI calculations at corporate headquarters assume that a project will be available through several years and likely through several new sets of features.

Later in this book, we discuss refactoring, the process of transforming existing software into better software by revisiting its various design assumptions and implementation strategies, and replacing them with more efficient versions. After learning an effective way to solve a problem, it is often tempting to race back to older code, rip half of it out, and replace it with a better implementation. Sometimes this philosophy leads to real benefits, and sometimes it leads to wasting time solving problems that aren't really problems.
 
Last modified on Wednesday, 23 September 2009 21:10
Vicky

Vicky

E-mail: This e-mail address is being protected from spambots. You need JavaScript enabled to view it