The Challenges of Web Application Development
Overview
This book will demonstrate the use of freely available Java Open Source (JOS) development frameworks for building and deploying applications. Specifically, we will focus on the JOS development frameworks available from the Apache Software Foundation's Jakarta group (http://jakarta.apache.org).
While most books are heavy on explanation and light on actual code demonstration, this book emphasizes approachable code examples. The authors of this book want to provide a roadmap of JOS development tools to build your applications. Our intent in this book is not to present each of the frameworks in minute detail. Frankly, many of the development frameworks presented in this book could have entire books written about them.
This book will build a simple application using the following Jakarta technologies:
- Struts Web Development Framework:
A Model-View-Controller-based development framework that enables developers to quickly assemble applications in a pluggable and extensible manner.
- Lucene:
A powerful indexing and search tool that can be used to implement a search engine for any web-based application.
- Velocity:
A templating framework that allows a development team to easily build "skinnable" applications, whose "look and feel" can be easily modified and changed.
- Ant:
An industry-accepted Java build utility that allows you to create sophisticated application and deployment scripts.
This chapter will not go into the details of the technologies listed above. Instead, it will highlight some of the common challenges in building web applications and explore some common design mistakes and flaws that creep into web-based application development efforts.
The truth is that, while all developers would like to write new applications from scratch, most of their time is spent in performing maintenance work on existing software. Identifying design flaws, referred to as antipatterns throughout this book, and learning to use JOS development frameworks to refactor or fix these flaws can be an invaluable tool.
Specifically, the chapter will explore how the following web-based antipatterns contribute to entropy within an application:
- Concern Slush
- Tier Leakage
- Hardwire
- Validation Confusion
- Tight-skins
- Data Madness
The chapter will end with a discussion of the cost savings associated with building your own application development framework vs. using JOS development framework.
Challenges of Web Application Development
In the mid-nineties, the field of software development was at its peak. The importance of using the Internet was quickly recognized as a revolutionary means for companies to communicate their data and processes not only to their employees but also to their customers.
Fueling the Internet explosion was the World Wide Web and the web browser. Web browsers offered an easy-to-use graphical interface that was based on standards and allowed easy to access to data on a remote server. Originally, the web browser was viewed as a means of allowing end-users to access static content of a web server. Early web applications were often nothing more than "brochures" that provided users browsing information about a company and the products and services it offered.
However, many software developers realized that the web browser was a new application development platform. The web browser could be used to build applications that provided customers with direct and easy access to corporate applications and data sources. This was a revolutionary concept because for many businesses, it eliminated the need to have a large customer service department to handle routine customer requests. It allowed them to make their processes more efficient and develop a more intimate relationship with their customers.
The ‘thin’ nature of the web browser meant that software could be quickly written, deployed, and maintained without ever touching the end user's desktop. Moreover, the web browser had a naturally intuitive interface that most end users could use with very little training. Thus, the Internet and the web browser have become a ubiquitous part of our computing lives and a primary application development platform for many of today's applications.
However, the transition of the web from being an electronic brochureware to an application development platform has not been without growing pains. Writing anything more than a small web application often requires a significant amount of application architecture before even a single line of real business logic is written.
The additional overhead for implementing a solid web application is the result of several factors, such as:
- The stateless nature of the web: HyperText Transport Protocol (HTTP), the communication protocol for the web, was built around a request/response model. The stateless nature means a user would make a request and the web server would process the request. But the web server would not remember who the user was between any two requests.
- The limited functionality of a web browser-based user interface: The web originally started as a means to share content and not perform business logic. The HyperText Markup Language (HTML) used for writing most web pages only offers limited capabilities in terms of presentation. A web-based interface basically consists of HTML forms with a very limited number of controls available for capturing user data.
- The large number of users that the web application would have to support: Often a web application has thousands of concurrent users, all hitting the application using different computing and networking technologies.
- The amount of content and functionality present in the web application: In equal proportion to the number of end users to be supported, the amount of content and navigability of a web- based application is staggering. Many companies have web-based applications where the number of screens the user can interact with and navigate to, is in the thousands. Web developers often have to worry about presenting the same content to diverse audiences (also known as internationalization).
- The number of systems that must be integrated so that a web application can give its end-user a seamless, friction-free experience: Most people assume that the front-end application that a user interacts with is where the majority of development work takes place. This is not true. Most web application development involves the integration of back-office applications, built on heterogeneous software and hardware platforms and distributed throughout the enterprise. Furthermore, extra care must be taken in securing these back-end systems so that web-based users do not inadvertently get access to sensitive corporate assets.
- The availability of web-based applications: Web-based applications have forced enterprises to shift from a batch-process mentality to one in which in their applications and the data they use must be available 365 days a year.
Early web-based development was often chaotic and free flowing. Little thought was given to building web applications based on application frameworks that abstracted away many of the "uglier" aspects of web development. The emphasis was on the first-to-market with their functionality. However, the size and complexity of web-applications grew with time, and many web developers found it increasingly difficult to maintain and add additional functionality to their applications.
Most experienced software developers deal with this complexity, by abstracting various pieces of the application's functionality into small manageable pieces of code. These small pieces of code capture a single piece of functionality and when taken together, as a whole, form the basis for an application development framework.
An application development framework can be defined as:
Important |
A collection of services that provides a development team with common set of functionality, which can be reused and leveraged across multiple applications. |
For web applications these services can be broken down into two broad categories:
- Enterprise Services
- Application Services
Enterprise Services
Enterprise services consist of the traditional "plumbing" code needed to build applications. These services are extremely difficult to implement and are outside the ability of most corporate developers.
Some examples of enterprise services include:
- Transaction management, to make sure any data changes made to an application are consistently saved or rolled back across all the systems connected to the application. This is extremely important in a web application that might have to process the updates across half a dozen systems to complete an end-user's request.
- Resource pooling of expensive resources like database connections, threads, and network sockets. Web applications often have to support thousands of users with a limited amount of computing resources. Managing the resources, like those named above, is essential to have a scalable application.
- Load balancing and clustering to ensure that the web application can scale gracefully, as the number of users using the application increases. This functionality also ensures that an application can continue to function even if one of the servers running the application fails.
- Security to ensure the validation of the users (authentication) and that they are allowed to carry out the action they have requested (authorization). While security is often considered an administrative function, there are times when application developers need to be able to access security services to authenticate and authorize an action requested by a developer.
Fortunately, the widespread acceptance of building applications based on application servers has taken the responsibility for implementing these services out of the hands of corporate developers. Enterprise-level development platforms, like Sun's J2EE specification and Microsoft's .NET, offer all of the functionalities listed above as ready-to-use services that developers can use in their applications. Application servers have minimized the amount of plumbing code that an application developer has to write.
This book will not be focusing on the services provided by J2EE and .NET application servers, rather it will be focusing heavily on the next topic, that is application services.
Application Services
The enterprise-level development platforms, such as J2EE or .NET, simplify many of the basic and core development tasks. While the services offered solve many of the enterprise issues (security, transaction management, etc.), they do not help the application architect with the often daunting task of building web applications that are maintainable and extensible. To achieve the goals of maintainability and extensibility, several challenges need to be overcome. These challenges include:
- Application navigation
How does the end user move from one screen to the next? Is the navigation logic embedded directly in the business logic of the application? Web applications, having a primitive user interface, can allow users to access and navigate through thousands of pages of the content and functionality.
- Screen layout and personalization
As web applications run in a thin client environment (with a web browser) the screen layout can be personalized to each user. Since user requirements are constantly changing, web developers need to adapt the look and feel of the application quickly and efficiently. Design decisions made early in the application design process can have a significant impact on the level of personalization that can be built into the application at a later date.
- Data validation and error handling
Very few web development teams have a consistent mechanism for collecting data, validating it, and indicating to the end user that there is an error. An inconsistent interface for data validation and error handling decreases the maintainability of the application and makes it difficult for one developer to support another developer's code.
- Reuse of business logic
This is one of the most problematic areas of the web application development. The reason being that the development team does not have a disciplined approach for building their business logic into discrete components that can be shared across applications. The developers couple the business logic too tightly to the web application, and resort to the oldest method of reuse, cut and paste, when they want to use that code in another application. This makes it difficult to maintain the business rules in a consistent fashion across all of the web applications in the organization.
- Data abstraction services
The majority of web application development efforts involve integrating the front-end web application with back-office data stores. However, data retrieval and manipulation logic is tedious code to write, and when poorly implemented, ties the front-end application to the physical structure of the back-office data stores.
Unfortunately, most developers either do not have the expertise, or are not given the time to properly address these issues before they begin application development. With the pressure to deliver the application, they are forced to "design on the fly" and begin writing code with little thought to what the long-term implications of their actions are. This may result in antipatterns being formed within their applications.
These antipatterns contribute to the overall complexity of the application and ultimately increase the presence of entropy within the code base. Often, they do not realize the impact of these antipatterns until they have implemented several web applications and are now trying to support these applications while developing new code.
In the following sections, we are going to introduce the concept of patterns and antipatterns. We will then identify some common antipatterns in web application development, based on the discussion above.
An Introduction to Patterns and Antipatterns
One cannot open a software development journal or go to the bookstore without seeing some reference to the software design patterns. While many software architects love to enshroud patterns in a cloak of tribal mysticsm, the concept of a software development pattern is really quite simple.
Design patterns capture software development patterns in a written form. The idea behind design patterns is to identify and articulate these best practices so as to help other developers avoid spending significant amount of time re-inventing the wheel. The notion of the design pattern did not originate in the field of software development.
Design patterns originated in the field of architecture. In 1977, an architect by the name of Christopher Alexander was looking for method to identify common practices in the field of architecture that could be used to teach others. The concept of design patterns was first applied in 1987 by Kent Beck and Ward Cunningham (http://c2.com/doc/oopsla87.html).
However, the embracing of the software development design patterns really occurred with the publishing of the now famous Gang of Four (GOF) book, Design Patterns: Elements of Reusable Object Oriented Software (Gamma, Helm, Johnson, Vlissides), Addison-Wesley, ISBN 0-201-63361-2. First published in 1995, this classic book identified 23 common design patterns used in building software applications.
The concept of the antipattern was first introduced in the groundbreaking text, AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis, John Wiley (ISBN 0-471-19713-0). The book examined common patterns of misbehavior in system architecture and project management. As we are going to explore various antipatterns associated with web application development, it is useful to look at the original definition (from the aforementioned book) of the antipattern:
Note |
"An antipattern is a literary form that describes a commonly occurring solution to a problem that generates decidedly negative consequences. The antipattern might be the result of a manager or developer not knowing any better; not having sufficient knowledge or experience in solving a particular type of problem, or having applied a perfectly good pattern in the wrong context." |
An antipattern is a means of establishing a common language for identifying poor design decisions and implementations within your application. Antipattens help identify poor design decisions and help give suggestions on how to refactor or improve the software. However, the suggestions associated with the antipattern are only that. There is no right or wrong way of refactoring any antipattern, because every instance of an antipattern is different. Each instance of an antipattern will often have a unique set of circumstances that caused the pattern to form. Web antipatterns focus on poor design decisions made in web-based applications.
It is not an uncommon experience for a developer studying an antipattern to stop and say: "I have seen this before" or to feel a sense of guilt and think, "I have done this before". Antipatterns capture common development mistakes and provide suggestions on how to refactor these mistakes into workable solutions. However, there is no single way to refactor an antipattern. There are dozens of solutions. In this book, we merely offer guidance and advice, not dogmatic principles.
The web development antipatterns that are identified and discussed throughout this book are not purely invented by the authors. They are based on their experience working with lots of development teams on a wide variety of projects.
Web Application Antipatterns
For the purpose of this book, we have identified six basic antipatterns that most Java developers will encounter while building web-based applications. The web development antipatterns to be discussed are:
- Concern Slush
- Tier Leakage
- Hardwired
- Validation Confusion
- Tight-Skins
- Data Madness
Since the original definition of an antipattern is a literary form of communication, we will discuss each antipattern in general. In addition, symptoms of the antipattern are identified along with suggested solutions. However, the solutions described in this chapter are only described in very general terms. Specific solutions for the antipatterns will be demonstrated, throughout this book, by the application of JOS development frameworks.
This book is written considering the following key points:
- Most developers are not architects. They do not have the time and energy to write the application architecture from the ground up and provide constant maintenance to it. Therefore, practical solutions using existing applications frameworks are more valuable than the code snippets demonstrating one part of the application architecture. So try to leverage other people's code. Every feature you use in application architecture is one less feature you have to write and maintain yourself.
- There are already several open source development frameworks ready for immediate use. Writing an architecture code might be an intellectual challenge for some developers. It is often a waste of time, resources, and energy for the organization employing them.
- Focus on the business logic. Most developers' job is to solve business problems. Every time you are confronted with writing a piece of code that is not directly related to solving a business problem, make sure whether or not you actually need to write it.
- Keep it simple. The most extensible and maintainable systems are ones that always focus on simplicity.
Important |
Architecture is done right when it has been implemented in the most straightforward fashion. Simplicity, above everything else, will guarantee the long-term maintainability and extensibility of an application. |
Now let's discuss the different web antipatterns in more detail.
Concern Slush
The Concern Slush antipattern is found in applications when the development team has not adequately separated the concerns of the application into distinct tiers (that is, the presentation, business, and data logics). Instead, the code for the applications is mixed together in a muddy slush of presentation, business, and data-tier logic. While development platforms like J2EE help the developer separate their application logic into distinct tiers, it is ultimately how the application is designed that determines how well defined the application tiers are. Technology can never replace good design and a strong sense of code discipline.
This makes the code extremely brittle. Changing even a small piece of functionality can cause a ripple effect across the entire application. In addition, every time a business rule needs to be modified or the structure of a data store changes, the developers have to search the application source code looking for all the areas affected by the change. This leads to a significant amount of waste of time.
This antipattern also tends to lead to insidious bugs creeping into the application, because invariably the developer will miss some code that needs to be modified. The bugs resulting from these missed changes might not manifest themselves for several months after the change to the original code was made. Hence, the development team has to spend even more time tracking down the missed code and fixing, testing, and redeploying it.
Most of the time the Concern Slush antipattern will emerge for one of the following reasons:
- Lack of an application architect
The development team does not have a senior developer playing the role of an application architect. The application architect's primary role is to provide high-level design constructions for the application. The architect establishes the boundaries for each of the application tiers. They enforce development discipline within the team and ensure that the overall architectural integrity of the application stays in place.
- Inexperience of the development team
The development team is new to enterpise development and writes its web applications without a thorough understanding of the technology it is working with.
Often the developers are used to writing code in a procedural language (such as C or Visual Basic) and are suddenly appointed to write web-based applications with an object-oriented language like Java. The development team members continue to rely on their original training and continue to write code in a procedural fashion, never fully embracing object-oriented design techniques.
- Extreme time pressures
The team realizes its mistakes during the development, but has been given an agressive deadline to be met. The developers toss caution to the wind and begin coding. They often do not realize how poorly designed the application is, until they begin the maintenance phase of the project.
- Using an application prototype as the base for development
Often, the development team will work together on a quick prototype for an application, as a proof of concept. The code for the prototype is poorly designed. However, upon demonstrating the prototype, it becomes a huge success. The developers now fall victim to this success as they are put under a heavy pressure to deliver the prototyped application quickly. Therefore, they decide to use the prototype code as the basis for the application.
Symptoms
For web applications based on the Java platform, the symptoms for this antipattern will usually manifest in one of two ways:
- Overloading of responsibilities
- Indiscrete mixing of presentation, business, and data logic
The first symptom, overloading of responsibilities, occurs when a single or small group of servlets or JSP pages are responsible for all actions carried out by the application. A basic tenet of object-oriented design is that each class within the system should have small, well-defined, and discrete set of responsibilities.
A class, in this case a servlet or JSP page, is overloaded when the exact responsibilities of the class are not clear. Servlets and JSP pages that do not have well-defined responsibilities are often said to be "fat" or "heavy". The call to such a page always includes a number of control parameters that are used by the servlet or JSP page. These control parameters are used by conditional logic embedded by the servlet or JSP page to determine the code to be executed within the page.
In the second symptom, a servlet or JSP page mixes together presentation, business, and data logic into one massive procedure call. An example of this particular symptom is out.write() statements mixed with business logic and data logic. JSP pages are even more prone to this abuse because JSP scriptlets make it extremely easy, for even a novice web developer, to quickly build an application.
In the second symptom, we are assuming that no session Enterprise JavaBeans (EJBs) are being used in the application. When EJBs are used in an application most developers will gravitate towards putting the business logic in the EJBs. The Concern Slush antipattern manifests itself in EJBs, when the developer indiscriminately mixes data-access logic with the application's business logic in the EJB.
Solution
The solution is to provide software constructs that adequately separate the application's code into readily recognizable presentation, business, and data logic. For Java-based applications, the JSP Model-2 architecture is the recommended architectural model for building web applications. The JSP Model-2 is based on the concept of a Model-View-Controller (MVC) framework.
In an MVC framework, all requests made by the end user are routed through a controller class (usually a servlet) that determines the business object used to carry out the request. The data that the users request and the corresponding business object are considered as a model piece of the framework. After the business object has processed the user's request, the results are forwarded by the controller to the view portion of the framework. The view portion of the framework is responsible for the presentation logic that renders the results of the user's request to the end user. The diagram overleaf presents a conceptual view of an MVC framework:
The two key features of the MVC framework are:
- The clean separation of the presentation, business, and data logic into self-contained software constructs. The MVC framework acts a natural roadmap, which helps the software developer ensure that they keep their application's logic broken into distinct pieces.
- The emphasis on building an application through declarative programming. Since all the access to presentation, business, and data logic is controlled through a single entity (that is, the controller), the developer can easily change the behavior of the application by changing the configuration data being fed to the controller. The application developer can completely "rewire" the code to display a different presentation interface or apply different business logic without having to touch the source code for the application.
Tier Leakage
The Tier Leakage antipattern occurs in applications that have been separated into three distinct layers of application logic. Tier leakage occurs when code and functionality from one tier are exposed to the other tiers.
This antipattern occurs when the application architect does not enforce the principle of "closed" tier architecture. A closed tier architecture allows each tier to communicate only with the tiers immediately above and below it. In other words, the presentation tier can only communicate with the business tier. It should never bypass the business tier and access data directly. The communication between the tiers happens via well-defined interfaces that do not expose the underlying implementation details of that tier to the one above.
In the case of Tier Leakage, the application architect breaks the application into three tiers, but they also allow communication between the tiers to be open. This means the presentation tier can still call and invoke services on the data-access tier. In addition, even if there is encapsulation of services, the underlying tier details still remain exposed. This allows the developers to bypass the application partitions put in place and use functionality they should not have access to.
The diagram below illustrates the differences between a closed and open tier architecture:
The end result of not enforcing a closed tier architecture is that, while various classes within the web application can be identified and grouped together in distinct tiers, there still exist dependencies between the tiers. This means that the changes to one tier can have side effects that ripple through the code in the other tiers.
This antipattern occurs when the development team has not defined discrete interfaces that hide the implementation details of one application tier from another. The causes for the Tier Leakage antipattern are very similar to those of the Concern Slush antipattern; developer inexperience, compressed delivery dates, and inappropriate reuse of an application prototype.
Symptoms
Some of the symptoms of Tier Leakage include:
- Changes to one tier break the code in other tiers
- You find that you cannot easily reuse a piece of code in one tier because of dependencies on a class in another tier
The first symptom is a common mistake. Instead of wrapping data retrieved from the data tier, the business tier exposes the details about the data tier, by allowing the data-tier objects to be passed back to the presentation tier. This results in the presentation class being unnecessarily exposed to the data-access technology being used to retrieve data (that is, JDBC, JDO, entity beans). It also tightly couples the presentation code to the physical column names, data types, and data relationships from the database. If physical details of the database change, developers need to walk through all of the code in the application to reflect the database changes.
The second symptom occurs when the developer allows tier specific classes to be passed back and forth between the different tiers. For example, you may have several classes, responsible for the business logic within your web application, which you'd want to reuse in a Swing based application. However, you cannot easily reuse the business logic, as it accesses an HttpSession object passed to it. The developer, rather than pulling the data out of the session object and then passing it to the business class, passes the HttpSession directly to the class.
Solution
There are three steps that can be taken to avoid tier leakage:
- Ensure that all the communication between the different tiers of an application takes place behind well-defined interfaces. Again, this means that one tier (say, the presentation tier) should only be able to access the tier immediately below it (say the business logic tier). In a Java-based web application, this can be accomplished through the judicious application of J2EE Design patterns. (We will be covering certain details of specific J2EE design patterns. For more information about this you may refer to J2EE Design Patterns Applied, Wrox Press, ISBN: 1-86100-528-8.) J2EE design patterns like the Business Object, Data Access Object, and Value Object patterns all do an excellent job of wrapping the implementation details of the classes within a particular tier. These design patterns will be described in greater detail in Chapter 5.
- Preform frequent code reviews. If you are using a version-control system, establish a process where nothing is checked into the version control system without another developer reviewing it. Provide a checklist of elements in the code that must be architecturally compliant. Make the developer who wants to check in the code, walk through the code changes they have made and have the reviewer compare this against the compliancy checklist.
This review is designed to be very short (no more than five minutes long). It forces the developers to verbalize exactly what they have written and give the reviewer a chance to catch tier-leakage mistakes before they creep into the overall code base.
- Leverage JOS development frameworks, such as Struts, to abstract away the implementation details of one tier from the other. These frameworks provide services that allow you to minimize dependencies between the application tiers.
While any one of the steps shown above can help minimize the risk of Tier Leakage, you will probably find that using all three steps combined is the most effective. As you will see in later chapters, even with application frameworks such as Struts, you will still need to apply the J2EE design patterns within your application.
Using a development framework can still create dependencies in your code if you are not careful. You can still end up having your application being too tightly bound to the application development framework. Chapter 5 will look at how we can leverage various J2EE design patterns to cleanly separate your application code from the development framework.
Hardwired
While the Tier Leakage antipattern dealt with dependencies being created at the architectural level of the application, the Hardwired antipattern occurs when developers create dependencies at the application level. Hardwiring arises when the developer does not provide configurable plug-in points for screen navigation and application business rules. These items are hard coded into the application source code; thus, any changes to functionality require the source code to be altered, recompiled, and redeployed.
The Hardwired antipattern makes maintenance of web applications difficult because:
- Web applications can have thousands of pages of functionality. Hardwiring the pages that a user can navigate to directly in the application source code creates tight dependencies between the pages. This makes it difficult to rearrange the order in which screens are accessed. It also makes it nearly impossible to break screens into independent entities that can be reused across multiple applications.
- The business rules for a web application are in a constant state of flux. There is an unrelenting demand by organizations to provide a personalized web experience to their customers. Therfore, hardwiring the creation and invocation of business rules directly to a particular page demands the constant modification of the application source code by the web development team of the organization.
The Hardwired antipattern develops because the web development team does not use a declarative approach to build its applications. A declarative design approach separates the application's "what happens" functionality from the application's "how it happens" functionality.
In a declarative architecture, the application is broken into small pieces of functionality that can be configured together using metadata. Metadata is essentially data about data. In most application frameworks, metadata is used to define how a user's request is to be carried out and processed by the framework.
Metadata is usually stored in configuration files, independent of the application source code. When the application development team needs to change the behavior of the application, they do it by changing the metadata configuration. Using metadata to control application behavior is often termed declarative architecture. In a declarative architecture, you define certain behavior and the architecture code carries out the behavior. By using this type of architecture, new functionality can be added or existing behavior modified by changing the metadata. Thus the behavior of the application is not hardcoded and does not require a recompilation and redeployment for the changes to take place.
The advantage of a declarative architecture is that it allows the development team to introduce new functionality into the application, while minimizing the risk of ripple effects that the change will have throughout the system. The disadvantage is that it can be overdone to the point where the application becomes over abstracted, hard to maintain because of the complex configuration rules, and suffers from poor performance.
Symptoms
The symptoms for the Hardwired antipattern begin to manifest themselves, when changes to the application require functionality that was not in its original scope. The symptoms of hardwiring include:
- The hardcoding of navigation logic directly within the applications source code. If your development team has to search through all of the application's source code for changing a link, your application is showing signs of the Hardwired antipattern.
- An inability to change the workflow of the application without a significant amount of refactoring of the application's source code. If the application you are writing always assumes that data captured from the end user is always entered in a certain screen order, then the application is hardwired.
- There is no consistency in how or when a particular screen invokes the business rules. This inconsistency makes it difficult to maintain the application's code and also means that new logic or functionality cannot be "swapped" into the application. This is symptom particularly common in projects with large development teams.
One of the true indications that your application is suffering from the Hardwired antipattern is that a small navigation or business rule change causes major headaches for you or your development team.
Solution
The Hardwired antipattern can be refactored, by taking the responsibility of writing the code for screen navigation and business rule invocation out of the hands of the application developer. Instead, this logic should reside as a service within the application architecture. Since this service is no longer a responsibility for the developer, consistency can be enforced among the entire development team and much of the application's navigation, workflow, and business rule invocation functionality can be described as metadata.
The MVC pattern is again an excellent tool for refactoring this antipattern. The controller of the MVC is responsible for application navigation. The business logic for the application is cleanly separated from the presentation logic. Metadata is used to tie all of these different pieces together.
Even if an MVC development framework is used, the only true way to guarantee that a Hardwired antipattern does not develop is through strong software development practices. These practices include:
- Judicious use of design patterns to ensure that hardwiring does not occur between your application code and the development framework you are using to build the application. We will explore these design patterns in greater detail in Chapter 5.
- Write an application framework development guide that explains to your development team, how the application framework is partitioned into different pieces. Clearly identify the architectural best practices and identify those practices that violate the integrity of the framework. The framework developer's guide must be constantly updated, to ensure that material contained within it matches the current implementation of the framework. Depending on the complexity of the project, your development guide might be something as simple as a set of UML diagrams explaining the major framework components along with some notes about any design patterns used. Do not always rely on the JOS framework documentation. JOS projects can have haphazard documentation.
- Use the application framework development guide as a tool during code and design reviews. Hold these review sessions frequently and hold the developers accountable for adhering to standards defined in the guide.
Do not become overzealous while avoiding Hardwiring in you applications. It is easy to want to make everything in the application configurable.
Important |
Good application architecture lies in its simplicity. You always have to negotiate between the need to generalize and abstract framework functionality and the need to avoid tight dependencies. In the end, over abstraction or tight dependencies both lead to the same problem: code that is too complex to understand and maintain easily. |
The Stuts development framework takes a declarative approach for writing applications. This framework allows you change the behavior of the application by modifying configuration files. In both of these frameworks, application configuration is very easy and is designed to avoid the over-abstraction problems mentioned above.
Validation Confusion
The Validation Confusion antipattern revolves around the inconsistent application of validation and business logic in an application. Many web application developers do not clearly separate the application's validation logic from its business logic in an organized fashion.
The end result is the application consisting of a mess of JavaScript and server-side code for handling data validations. The data validation code is split between the front-end screens and also embedded within the business rules that carry out end-user requests. Logic for handling end user errors is often inconsistently applied and mixed with the business logic.
For the purpose of this book, validation logic is defined as any type of user interface code that involves:
- Formatting of data being presented or collected from the end user.
- Checking to ensure the user entered the required data.
- Type-checking to ensure that the data entered is the appropriate type. For instance, you want to make sure that when the user is asked to enter numerical data in a field, they do not enter character or strings (such as "abcd") as a numerical value.
- Simple bound checking logic to ensure that the data collected falls within a certain range (whether it is numeric or date data being collected)
Validation logic is considered extremely "lightweight". Validation rules are considered light, because changing them should not have a significant amount of impact on the overall business processes supported by the application. Business logic is the "heavyweight" cousin of validation logic. Business logic supports business processes. Changing this logic can have a significant impact on how a business is operated.
Why worry about the separation of validation logic from business logic? Failure to separate these two types of logic from one another makes it difficult to support the code. Since the validation logic is not centralized, developers have multiple spots to check when modifying a business rule.
More importantly, not cleanly partitioning the application's validation logic from its business logic can make it more difficult to reuse that business logic in another application. Validation rules are often very specific to an application. Business logic can be abstracted, generalized, and reused across multiple applications. However, with validation rules specific to the application embedded inside the business logic, a tight dependency is created that makes code reuse problematic.
A clean validation logic approach can help avoid previous antipatterns mentioned, namely Concern Slush and Tier leakage. The validation layer can be responsible for adapting the input provided by the user interface to the input required by the business logic. This can help prevent the user interface details from leaking down into the business logic.
This antipattern occurs when the web development team has not clearly defined how it is going to handle the validation of the data collected from the end user. The developers pass all of the data directly to the business rules in the application, without first putting the data through some kind of filter that ensures the data validity.
Symptoms
Validation Confusion can be spotted in one of the following cases:
- When asked where a particular validation rule for a screen resides, a developer has to search through presentation (as a language like JavaScript or JSP scriptlets) and business tier code to find the exact spot for the rule.
- The development team needs to constantly refactor code, because application specific validation rules are embedded inside the business logic it wants to reuse.
- There is no consistent mechanism for "how" validation errors are handled. End-users encounter different formats for errors being displayed. For example, in an application with Validation Confusion some of the errors might be displayed directly in the web browser while other errors will pop up in JavaScript alert windows. In short there is not consistency in the error handling that the end user experiences.
Solution
Refactoring the Validation Confusion antipattern can be accomplished by defining a consistent set of services used for form validation in the web application. These validation services are invoked before any of the business logic for the application is invoked. Any validation errors that occur are immediately processed and the end user is notified in a consistent and repeatable fashion.
This means that the validation for the application only resides in one tier of the application, using a consistent mechanism, for invoking the validation rules. This might mean having all of the application validation logic reside in a standard set of JavaScript class libraries, or, as is the case with Struts, moving all validation logic for a form to a set of Java classes that are invoked whenever the user submits data.
In Chapter 3 we will discuss the mechanism provided by Struts for handling form validation and errors.
Tight Skins
Web-based applications have the ability to deliver unprecedented amounts of personalized content to the end user. Traditionally, companies pushed information out to their customers in a mass marketing approach. In this approach, customers were categorized into broad groups who shared similar interests and backgrounds. The company would then direct different advertising messages about their products to these groups. Mass marketing approach was considered successful if the organization running marketing campaign received a response rate of one percent.
The web development platform, with its thin-client, easy-to-use, and personalizable interface, has turned the mass marketing concept on its head. Web-based applications can deliver tightly-focused information and functionality to individual users, with very specific preferences and interests. Many of the sophisticated web applications that are currently online:
- Allow the end user to choose the information and content that they want to see
- Personalize the color, font, and layout of the web application user interface to reflect the user's personal choices
- Reach a global audience by having the web application presented in their language, using a look and feel appropriate for the end user's culture
However, the ability to deliver a customizable user interface to the end user requires some careful planning, in the design phase of a project. The Tight-Skins antipattern is a presentation-tier antipattern. It forms when the development team has not built its presentation tier to be flexible enough to handle personalized content for individual end users.
This antipattern can occur for a number of reasons:
- The original requirements of the application did not include an extensible user interface. However, requirements for the application changed. Since the development team had not planned interface flexibility upfront, it now has to face the challenge of refactoring the presentation tier of the application to support it.
- The development team was too focused on reuse at the business and data tier. It wrote the presentation tier in a monolithic fashion that did not have a component structure. Most developers are very comfortable thinking in terms of generalization, abstraction, and extensibility for server-side logic. However, the presentation code is often written with no real attempt to "templatize" it into components that can be easily swapped in and out of the application.
- The development team used the presentation code from the application prototype (if there was one) for the production application. This is usually done to save time and is again a reflection of the lack of design consideration for the user interface.
Unfortunately, the only way to combat the Tight-Skins antipattern, after it is formed, is to rewrite the user interface from scratch. This is why it is critical to identify personalization requirements for the application before any serious development work begins.
Symptoms
This antipattern has a number of symptoms including:
- The application's content is not separate from the application code. If you need to modify your application's source code to change the content delivered to the end user, this is a definite sign of a Tight-Skins antipattern. A common example would be when a JSP page has a significant amount Java scriptlets code and HTML mixed together. There are chances of tight coupling between some of the contents of the page and the JSP scriptlets.
- The application screens are not based on templates. Designing your application's screen so that it is divided into discrete components (that is, the header, the footer, navigation bars, etc.), without which you will find yourself propagating the same change across multiple screens.
- The presentation tier is hardcoded in one language. Many web applications start to support only one group of people. All content for the application is written in only that language. If the development team has to support multiple languages, it usually has to scour all code for any content that will be displayed to the end user then convert them over to the new language. This is especially painful, if more than two languages have to be supported.
Solution
The solution for the Tight-Skins antipattern involves cleanly separating application content from your Java source code. This way the content can be presented in multiple formats without having to wade through code. This also makes it easier to change how the content is to be displayed to the end user. Some ways of separating the application's content from its source include:
- Using JSP tag libraries to completely abstract any Java code from a JSP page. This way presentation content can easily be changed without having to wade through Java application code.
The Struts framework makes heavy use of custom JSP tag libraries. Some of the Struts tag libraries will be covered in greater detail in Chapter 3.
- Separating the application's content by making it external to the application's source code. Struts allows you to separate screen content and messages in a file independent of the application. This way, content can be changed without having to change the actual JSP page. This material will be covered in Chapters 3 and 4.
- Building your application's screens using the concept of a template. Templates divide the application screen into small components that can be easily plugged in and out. They also allow common elements in the presentation to be shared across all of the screens in the application. Velocity uses a templating language to accomplish this. Using Velocity makes it impossible to embed Java code inside the application. Chapter 6 of this book will introduce the reader to the Velocity templating language and its various uses.
Data Madness
Most web developers know that embedding data access logic inside of presentation code is poor design. Applications written in this fashion are difficult to maintain, and are tightly coupled with the underlying data structure of the database that they are manipulating. A change to the database can cause many elements of the user interface to be visited and often modified.
Many Java-based web development teams never allow the presentation layer of an application to directly obtain a database connection and use it to access a data store. Instead, they always wrap these calls inside the business tier. The development team never breaks out the Create, Replace, Update, and Delete (CRUD) logic associated with manipulating the data into a distinct set of classes. Instead, they intermix business and data access logic together inside the business tier.
The Data Madness antipattern forms when the application's architect does not decide how data-access logic is to be abstracted away from the other tiers in the application. When building a data-access tier, the following items have to be considered:
- How data is going to be accessed and manipulated
- Mapping relational data to Java-based objects
- Abstracting away physical details and relationships of the underlying data store
- Wrapping non-portable, vendor-specific functionality
- How transactions are going to be managed, particularly, transactions that cross multiple objects manipulating data from a data source
As most developers do not think of a data-access tier while designing, the formation of a Data Madness antipattern can significantly increase the amount of time and effort needed to complete a project. Consider the following:
- Most database access in Java is accomplished via the JDBC standard.
- The JDBC standard uses standard SQL code to retrieve and manipulate data from a relational database. It is very easy to write poorly behaving SQL. Most Java developers find it tedious to write and maintain a small task, like retrieving some data from a table that requires a lot of code.
- The JDBC API, while using Java objects in the actual API, does not take an object-orientated approach to data access. JDBC uses a relational model that retrieves data in a very row- oriented relational manner. This method of access is very clumsy and time consuming for a Java developer to work with.
- In a medium to large application, a significant amount of a developer's time can be spent doing nothing more than writing JDBC code to access data.
A significant amount of a development team's time is taken up writing data-access code (usually SQL code). Code that does not fit an object-oriented development model is prone to be coded improperly, and is scattered haphazardly through an application's business tier. This is the crux of the Data Madness antipattern.
Symptoms
Most development teams do not see the symptoms of the Data Madness antipattern until they are well along in their development efforts. The first symptoms of the Data Madness antipattern include the following:
- The repetition of the same data access logic within several business logic classes. This symptom is particularly prevalent in large development projects where very little development work is being done in the database (there are no stored procedures, triggers, or queries being executed inside the database). Developers are left to write their own data-access code, and often two developers will go after the same data for use in two different areas in the application and end up with almost identical data-access code.
- The dawning realization by the members of the development team that they are seriously behind schedule on the project. Upon examination they find that most of their efforts are spent writing database code.
- The sudden appearance of data-access helper classes and "homegrown" persistence frameworks within the application source code. These helper classes might help reduce the amount of code the developer is writing, but they do not solve the overall architectural issues of not having a well-defined data-access tier.
Later symptoms of the Data Madness antipattern usually appear in the maintenance and enhancement phase of the project:
- A database has to be reorganized for performance reasons. Several table relationships change and the development team faces a daunting refactoring project, as it has to pour through all of the source code and make modifications to reflect the underlying database change.
- The developers try to port the application to a new database platform, and have found that several key pieces of logic are relying on vendor-specific functionality. For example, one of the most common problems areas is the generation of primary keys for a database. Without a well-designed data-access tier, moving an application from SQL Server to Oracle can be a coding nightmare. SQL Server has the concept of auto-incrementing columns, while Oracle does not; it has sequence objects. This means that to port the code you need to find every SQL statement that uses sequences and change it. This is not a small task in a large project.
With a well-defined data access strategy in place, the development team could have abstracted how primary keys are generated, and centralized all of this logic in one class responsible for primary key generation.
- The development team wants to refactor the application to use the latest and greatest technology. (Java Data Objects, web services; you choose the buzzword.) Since the technology used to retrieve and manipulate data is not abstracted away from the classes using the data, the development team must again perform search and replace missions to find all code that uses the existing technology, and replace it.
Solution
Two steps can be taken to refactor the Data Madness antipattern:
- Provide a clearly defined data-access tier, which provides services that the business tier can use to access data. These services should abstract away the physical details of the database being accessed, any vendor-specific APIs being used, and how the data is actually being retrieved.
- Avoid writing data-access code, whenever possible. Use technologies that will let the developer map the underlying database tables to Plain Old Java (POJ) objects. These significantly reduce the amount of code that the development team must write and let it more clearly focus on the functionality of the application.
The first step above is a design-based approach involving the use of common J2EE data-tier patterns, like the Data Access Object and Value Object patterns, to abstract away database and data access details. These patterns are extremely easy to implement and when used help the development team maintain data-tier code without affecting the rest of the application.
The second step is a technology-based approach. Java is an object-oriented language that is not well suited to deal with the table-centric structure of relational databases. Instead of having the development team write its own SQL code, use an Object Relational (O/R) mapping tool to perform Create, Retrieve, Update, and Delete (CRUD) actions on behalf of the developers.
O/R mapping tools allow the development team to declare how data retrieved from the database maps to Java objects. O/R mapping is not a new concept. The J2EE API supports the concept of Container-Managed Persistence (CMP)-based entity beans. CMP-based entity beans allow the developer to provide O/R mappings to the J2EE application server and in turn, the application server generates all of the SQL code needed to access the database.
An alternative to entity beans is to use commercial O/R mapping tools. These tools have been available for years to C++ developers and have started gaining a significant amount of acceptance from the Java development community.
Commercial O/R mappings tools, while being very powerful, often carry long and expensive licensing agreements. They are often complicated to use and being a commercial product require a heavy investment in training, before the development team becomes proficient in their use.
However, over the last two years, JOS O/R mapping tools have started gaining more and more acceptance as an alternative means of building data access tiers. In Chapter 5 of this book, we are going to examine how one such JOS O/R mapping tool, ObjectRelationalBridge can be used to solve many of the problems created by the Data Madness antipattern.
Antipatterns, JOS Frameworks, and Economics
When web antipatterns form in an application, the cost of building and maintaining that application grows substantially. The development team's time is eaten up with the complexity that has crawled its way into the application. Less time is available to write real code and the code that is written is usually of mediocre quality.
Why are these antipatterns allowed to form? Developers do not purposely write poorly designed applications. We believe that web development frameworks can significantly reduce the occurrences of web antipatterns forming within an application. Antipatterns sometimes appear because applications are extremely complex to build and implement. Developers do not purposely go out and introduce these antipatterns. These antipatterns often occur because the developers try to manage the complexity of just implementing the application's business logic. At times, they do not realize that the decisions they make now will come at a high price later when an antipattern manifests itself.
A well-designed web development framework will promote consistency and structure for the development team. A framework will provide core application services for screen navigation, data validation, error handling, business rule management, and data persistence. With all of these benefits why haven't more Java web developers adopted the use of web development frameworks in their application development efforts? The reasons vary:
- Writing a web development framework is expensive
- Writing a development framework usually requires senior developers and architects with a significant amount of design expertise
- Development teams have not been able to build a business case for spending the money necessary to build an application framework
- The development team had to spend the time necessary to maintain the development framework
Until recently, open source development frameworks have not been readily available to developers. This meant that if a development team wanted to use a framework, the developers needed to build it themselves. Writing a homegrown development framework can be an expensive undertaking. It usually requires a group of senior developers with several months of uninterrupted time to design, implement, and thoroughly test the development framework.
Most IT organizations do not have senior developers and architects sitting around with nothing to do. Usually these individuals are extremely over allocated and giving them the time to focus on one problem requires commitment from the highest level of management. Even after the framework is completed, additional ramp-up time is needed as the framework developers begin training the development teams in how to use the framework.
For example, the Struts framework has a significant number of services embedded in it. To write an in-house version that offers even a fraction of the services offered by Struts, you have to take into consideration the resources contributed to the Struts framework:
- Struts was built by some of the finest developers currently in the industry. Many of these individuals were senior Java developers who command extremely high salaries.
- The Struts framework has had literally hundreds of individuals testing and debugging the framework. Most organizations could not even begin to provide a Quality Assurance (QA) team that could thoroughly debug a framework like Struts.
- Struts is now a mature framework that has literally hundreds of client implementations all running on a wide variety of hardware and Java platforms.
For an organization to build a framework like Struts for internal use with the same level of sophistication and quality assurance could literally cost between a half a million and a million dollars.
Let's not forget that even after a custom framework has been built, the costs of the framework continue to accumulate, as you begin to factor in the development resources needed to maintain and support the framework code base.
For organizations building their own application frameworks, it can take up to year or a year and a half before the organization starts seeing firm return on investment from its framework development efforts. (This includes the time needed to develop the framework and actually build two or three applications using the framework.) This is simply too large of a leap of faith for most companies to make.
Java open source development frameworks offer a viable alternative to building your own application architecture. These frameworks provide the following advantages:
- They are free to use. Most JOS frameworks have a liberal licensing agreement that lets you use the framework free of charge for building your applications. The only real restrictions that come into play with open source development tools is that the group sponsoring the tools places restrictions on repackaging the tools and selling them as your own.
- They are well supported. All of the open source development frameworks covered in this book enjoy a significant amount of support. High priority bugs that are discovered within the framework are usually fixed and the fixes made available for general use within hours. In addition, mailing lists and Internet news groups offer a wealth of information on how to solve common development problems encountered with JOS development frameworks.
This information is free of charge and unlike most commercial software products does not require an annual support contract. Some open source projects have groups willing to sell support for the product. (Such as, the JBoss Group at http://jboss.org, which not only builds the JBoss application server, but also offers different levels of paid support for the project.)
- They are extensible. If you find there are features lacking in the framework you have chosen, there is nothing stopping you or your development team from extending it. The source code is readily available for modification. Many of the features found in open source frameworks started out as the result of challenges encountered by developers using the framework. The developers extended the framework to handle their problem and then donated their solution back to the framework's code base.
There are a few downsides with open source development frameworks that should be noted:
- Documentation for an open source framework can be extremely vague. People writing the frameworks are donating most of their time and energy to do something that they love: write code. But the same level of attention is not paid to the mundane, but equally important task of writing documentation. Occasionally, a JOS development framework does require the developer to crack open a debugger to figure out what the framework is doing.
- Open source frameworks tend to be very Darwinistic when it comes to features in the framework. High priority bugs in the JOS frameworks are often found and fixed immediately. However, bugs that are of a low a priority for the JOS framework developers might never be fixed. This can be problematic for a development team using the framework that needs that particular bug fixed.
- JOS development frameworks are relatively new technology. Things can still go wrong with them and then cause unexpected behavior in your application. It is imperative that if your development team is going to write mission-critical software with a JOS framework, it needs to perform a significant amount of testing. In addition, the developers need to ensure that the framework that they have chosen to use is supported by a vibrant development group that actively supports their code.
Open source frameworks free a development team from having to invest its time in writing infrastructure code. Infrastructure code is the entry price you must pay before you can seriously begin writing an application. From the authors' anecdotal experiences, in many projects, up to 40-60% of development effort involve the implementation of infrastructure code. For the "we don't have time for architecture" development teams, that 40-60% of infrastructure development effort is usually spent in increased maintenance of the application over the course of its lifetime.
Important |
Trying to cut costs by implementing complex architectures shifts the upfront architect and infrastructure costs to the maintenance phase of the application. |
Ultimately, leveraging the functionality in open source frameworks translates into three direct benefits:
- Less complexity for the application developer in writing their applications
- More focus on writing code that has a direct benefit to the organization
- A significant cost-savings, by allowing the development team to access a significant amount of functionality without having to pay a dime for it
Both the benefits listed above allow the developers to produce higher quality code and deliver their applications more quickly to their end users.
From a management perspective, there are still some items to consider before you use a JOS development framework on your projects:
- Using a Java Open Source framework does not eliminate the need to have an application architect or architecture team. You still need individuals who can support JOS framework questions and issues.
- The initial adoption of a JOS framework does require extra time to be built into a project plan. The development team is going to need time to learn how to effectively use the JOS framework. This means that the first one or two applications built on the framework might take more time than they would have taken without using the framework.
- For the first application built on the framework, you should have someone who is experienced with the framework to mentor your team. This might require bringing in outside consulting resources. Consulting costs will be market rate if the JOS framework chosen is widely known and used (that is, Struts). For more obscure JOS frameworks, consulting costs could be significantly higher.
The JavaEdge Application
As stated earlier in this chapter, the purpose of this book is to provide a simple and straightforward roadmap that demonstrates how to successfully use the Jakarta web development frameworks. To do this, we are going to build a simple WebLog application (also known as a Blog). A WebLog, in its simplest form, is an electronic bulletin board where one user can post a story and other users can comment it. Often, a WebLog ends up to be a combination of reports on real-world events with a heavy dose of editorial bias from the storywriters and their commentators. Our WebLog is called the JavaEdge.
The requirements for the JavaEdge application are:
- Visitor Registration
Individuals who visit the JavaEdge Blog can register themselves to be the members of the JavaEdge community. By registering, the users can receive the weekly JavaEdge newsletter.
- Browse Stories
Visitors to the JavaEdge web site will be able to see the latest top ten stories posted by the JavaEdge community. When browsing the stories, the JavaEdge user will be able to see a short summary of the story. Clicking on a link next to each story will bring up the complete story listing.
- Browse Comments
When a user clicks on a link next to each story, they will be presented with not only a complete listing of the story they have chosen but also all of the comments associated with that particular story. Each posted comment will display the comment text, when the comment was posted, and who posted it.
- Post Stories and Comments
Individuals can post a story or comments for an already existing story. If the individuals choose to register themselves as JavaEdge members, any stories or comments posted by them will show the name they provided during the registration process. If they do not register as the JavaEdge members, they can still post stories and comments, but their name will not appear next to the story. Instead, the story will be posted as from a anonymous user.
- User Registration
Users can register to become the members of the JavaEdge community by providing some simple information (such as name, e-mail address, user-ID, password, etc.)
- Search capabilities
A user can search all the stories, posted on the JavaEdge web site, using a simple key word search engine. Any hits found by the search engine will be displayed as a list of URLs
The application code for JavaEdge is relatively sparse because the authors wanted to focus more on the underlying open source frameworks than building a full-blown application. In addition to demonstrating the capabilities of the Jakarta Java development frameworks, the application will illustrate some basic design principles that will ensure the long-term maintainability and extensibility of the JavaEdge code base.
Summary
Not every application developer needs to be an architect. However, all application developers need to have some basic understanding of software architecture. Otherwise, it is easy to make common design mistakes, which form into antipatterns that can make code difficult to support and extend.
This chapter has identified six common antipatterns that often spring up in web-based application development. These antipatterns are:
- Concern Slush
- Tier Leakage
- Hardwired
- Validation Confusion
- Tight-Skins
- Data Madness
Along with descriptions of these antipatterns, general solutions to these antipatterns were discussed. A common theme that has formed throughout our discussions of solutions is that JOS development frameworks offer a structured mechanism to develop applications and minimize the amount of infrastructure code being written. Developing an application framework is an expensive proposition. Open source frameworks have the advantage of being:
- Free of charge
- Supported by a large and enthusiastic development community
- Easily extended to support new features and functionality
We also discussed the requirements of the JavaEdge Application that we are going to develop in this book.
The rest of this book will demonstrate the technique to use the following open source frameworks to refactor the antipatterns discussed earlier:
- Struts web development framework
- Turbine/Velocity framework
- ObjectRelationalBridge (OJB)
- Lucene
- Ant
After reading this book you should have:
- A working definition of what an application framework is, the knowledge of the costs and efforts of building an application development framework, and the attractiveness of the open source framework.
- The ability to identify web antipatterns within your own projects. You should be able to understand the root causes of these antipatterns and the long-term architectural implications of the antipatterns.
- An understanding of what steps need to be taken to refactor the antipatterns.
- The ablity to use common JOS development frameworks, like Struts, to refactor these antipatterns out of your applications.
- A comprehensive set of best practices for each of the frameworks discussed in this book. These best practices will cover a range of topics including what are common development mistakes when using the framework and what design patterns can be used to supplement the services offered by the framework.