As we mentioned in Chapter 1, one of the principles of modern application development is that an architecture-first process should be used to create systems. The term application architecture has different meanings for different people. It has been called a science, an art, and many other things that aren't quite as nice. Primary drivers behind the modern application architecture movement, including Grady Booch in his "Software Architecture and the UML" presentation at UML World 1999, have defined application architecture as encompassing a set of significant decisions about the organization of a software system. These decisions include:
Booch's architecture definition combined with Mary Shaw's definition of architectural style provide insight into what architecture is. In Software Architecture: Perspectives on an Emerging Discipline (Prentice Hall, 1996), Shaw notes that architectural style defines a family of systems in terms of a pattern of structural organization. The architectural style definition is:
With Booch and Shaw's concept of architecture in mind, we can gain additional insight from Booch's identification of the following characteristics of a good architecture:
Without a doubt, constantly evolving new technologies, rapidly growing organizations and markets, ever-shrinking IT budgets, and the incredible expansion of the Internet raise new concerns and create new priorities for developers of application architectures. In this section, we look at these concerns and at ways to address them when creating an application's architecture.
Given the size of enterprise applications, it makes sense to develop an application architecture that reuses as many components as possible. Reuse is an underlying assumption in most application models and should not be treated as a new technology or a technical breakthrough. Developers have been reusing code since the early days of application development. Categories of reuse include:
The reuse principle should not be limited to application code. Following a standard development process on multiple projects or leveraging the artifact structures created during one project in another project are examples of the efficiencies to be gained through reuse. Some of the best examples of reuse are internal applications that later made the transition to commercial products and were released as toolkits, systems, or environments. Examples of this type of reuse include COM/COM+, network directory services, and Microsoft products such as ActiveX Data Objects (ADO), Microsoft Transaction Server (MTS), Microsoft Message Queuing Services (MSMQ), Exchange, and SQL Server. These products now provide developers with significant advantages when creating applications.
The cost of creating reusable object components within an organization is significant. The additional overhead of creating and supporting object components over the long-term makes object reuse an expensive proposition for most non-commercial organizations. The cost of designing for reuse is often the primary driver behind the "buy it, don't build it" mindset.
Figure 2.1, which is based on a diagram in Walker Royce's Software Project Management: A Unified Framework, provides general statistics on the cost and schedule impact of creating reusable object components.
Figure 2.1 Cost and schedule investments necessary to achieve reusable components
Reuse can also occur at the level of the application itself. As we discuss later in this chapter and throughout Part 3 of this book, the N-tier, or multi-layer, architecture model provides the basis for solid internal application reuse. Separating services into multiple tiers provides interface points between the lower and upper portions of an application that can be used by any portion of the application functioning at a given layer. One of the benefits of this internal application reuse is that, as particular services need to be modified, maintaining the services interface means that other portions of the application will not require modification.
With the development of new application modeling techniques, another form of reuse has become possible: design reuse. Application logic and algorithm reuse have existed for many years. Many developers remember implementing their first search algorithms. Although they did not invent the algorithm, they were quickly able to implement the code based on someone else's intellectual work. With most software reuse, developers typically reuse specific code after the application architecture has been decided and the code is being implemented. The developing science of software patterns has led to design reuse and object class reuse that may or may not entail code reuse, depending on the origin of the pattern.
Because today's enterprise applications are so big, controlling their size has become an issue. The most efficient way to reduce the size of an application is to reduce the number of lines of human-generated source code. An important distinction here is reducing the amount of human-generated source code vs. reducing the total amount of source code. With today's modern development tools, the number of source code lines in an application often increases and therefore requires additional computer processing. However, component-based development models decrease the number of human-generated lines of code by incorporating reuse of objects and code, object-oriented systems, high-level development languages, and automatic code-generation tools.
Over time, as the use of higher-order application languages has increased, the efficiencies of these languages and their libraries has resulted in fewer lines of human-generated code. In his book Software Project Management: A Unified Framework (Addison-Wesley, 1998), Walker Royce compares the number of lines of code needed to write the same routine with various programming languages and tools, stating that
these values represent the relative expressiveness provided by various languages, commercial components, and automatic code generators.
Table 2.1 summarizes this comparison.
Table 2.1 Lines of code produced by various languages and tools
|Human-generated lines of code||Programming language and tools|
|175,000||C++ or Ada 95|
|75,000||Integration of third party commercial systems/components and C++ or Ada 95|
Object-oriented technologies and visual modeling tools also decrease the amount of source code that must be created by individuals. There has been much discussion on the efficiency of complex modeling languages, such as Unified Modeling Language (UML) and new visual modeling systems. Although these systems help to decrease the number of human-generated lines of code, they require significant investment in training to be used efficiently.
An application's architecture needs to take performance requirements into account and set steps in place to ensure that the application meets those requirements now and in the future. The application must be tested to determine whether it meets requirements, and if it doesn't, ways to tune the performance must be found. Performance requirements are rarely met without some tuning, so performance must be considered up-front and throughout the application project's life cycle.
Considering performance does not mean micro-optimizing every component. What is important to the application's users is overall system performance, not the performance of each individual component. Distributed applications have many variables that can influence performance—hardware, communications links, system software configuration, application topology, and so on—regardless of how components and applications are coded. Tradeoffs will always exist between ease of development, deployment, maintenance, and performance. The key to performance tuning is to do the minimum amount of work required to identify and eliminate—or at least reduce—bottlenecks in the overall system so that the application meets its performance requirements.
During performance validation, the conditions in which the application will be deployed can rarely be exactly duplicated, especially early in the project's life cycle. Thus, validation is a matter of extrapolating expected performance from the results of a series of controlled tests in environments resembling the deployment environment. The more closely the test environment resembles the deployment environment, the more likely that the application will meet performance requirements. However, the cost of creating such a test environment can greatly outweigh the benefit of reducing the risk that test results might be incorrect. Again, the key is to do only as much work as necessary to meet performance goals with an acceptable degree of confidence and then stop. Performance validation is about reducing the risk that the application will not achieve a necessary level of performance, not about tuning the application to the ultimate level of performance.
An application's architecture must accurately describe not only the size of the application but also the hardware setup, or distribution topology, necessary to meet the application's user requirements. An application's distribution topology defines the type, number, and configuration of server machines the application will run on. The key to building scalable, reliable applications is location transparency. Location-transparent applications can run on multiple servers to handle additional load, with no loss in performance. Applications, or portions of an application, can also be replicated across a cluster to provide better reliability in the event that a server fails. (A cluster is a group of physical computers that logically acts as a single computer.) The appropriate topology is selected based on the existing corporate computing infrastructure and policies, results of performance tuning, and any implementation-specific constraints on location transparency.
Most distributed applications are deployed into an existing computing infrastructure, so certain aspects of the topology might be predetermined. For example, an application might use an existing database running on a dedicated database server, and corporate policy might prohibit applications from being installed on that server. In such a case, the application's architecture must allow for its deployment on a different server and ensure that the appropriate communications protocols are in place, that connectivity exists between the servers, that the application can access the remote database with appropriate security credentials, and so on.
Corporate computing policies can also have a big impact when deploying applications that will be accessible to external users over the Internet. Typically, most servers will run behind a firewall to prevent random external users from accessing confidential information. There may be multiple levels of firewalls, each with different restrictions on who can penetrate the firewall and what type of communication is permitted. Web servers, applications, and database servers might need to be deployed behind different firewalls. Here again, appropriate communications protocols, physical connectivity, and security credentials must be available between the computers.
Although the existing computing infrastructure and policies might impose some constraints on how the application is scaled, the results of performance testing and tuning can influence the topology. For example, performance goals might be achieved with a single server that meets minimum processor speed, memory, and disk access speed requirements, or testing might indicate that the only way to meet performance goals is by scaling out. Scaling out means adding servers to the topology to distribute the client load. Applications can also be scaled out by adding additional business object servers, adding database servers, or partitioning data access.
Scaling out is often an attractive solution to the challenge of meeting performance goals because it improves performance without requiring any code changes to the application. The cost of adding hardware to the application topology is usually much less than the development and testing costs associated with changing application code, especially relative to the performance gains. Scaling out might increase administrative overhead, but the performance benefits will typically outweigh the administrative costs.
One of the great difficulties involved in developing an application architecture is finding a common terminology to express new concepts and apply existing concepts. Communication difficulty exists on both the development and organization levels and can be addressed using the tools discussed in this section.
As we've said, one of the most important factors in delivering successful applications is being able to communicate process, business, and technical information to all the people involved. The Unified Modeling Language (UML) can provide the common language with which to communicate and build understanding. Its primary purpose is to help organizations visualize, specify, create, and document the artifacts of a software system.
UML evolved from several primary modeling languages that were prevalent in the late 1980s and 1990s. Version 0.8 began as a combination of the Grady Booch method and James Rumbaugh's OMT method; Version 0.9 saw the incorporation of Ivar Jacobson's OOSE method; and Version 1.0 evolved when a large group of partners built upon the previous versions and incorporated new pieces. Further additions and approval by Object Management Group (OMG) have increased UML's status as a strongly supported, industry-standard modeling tool.
A complete description of UML is beyond the scope of this book, so this discussion serves only as a reminder of its key features. UML can be separated into four sections:
An understanding of UML is critical to the development and communication of successful application architectures. Several good textbooks are available on this topic, including The Unified Modeling Language User's Guide (Addison-Wesley, 1998) and Tried and True Object Development: Practical Approaches with UML (Cambridge University Press, 1999) by Ari Jaaksi, Juha-Markus Aalto, Ari Aalto, and Kimmo Vatto.
Another approach to describing and communicating complex application architectures involves the use of design patterns. The principles of design patterns were first applied to building architecture by Christopher Alexander, Sara Ishikawa, and Murray Silverstein in A Pattern Language: Towns, Buildings, Construction (Oxford University Press, 1977). In 1995, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides applied these principles to software engineering in their foundation book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995), where they stated that the technique:
…identifies the key aspects of a common design structure that make it useful for creating a reusable object-oriented design.
Table 2.2 Static and dynamic UML diagram views
|Static||Use case||Built in the early stages of development to capture system functionality as seen by the user. Their purpose is to specify the context of a system, capture the requirements of a system, validate a system's architecture, drive implementation, and generate test cases. |
Typically generated by analysts or experts in a particular problem or industry domain.
|Class||Captures the vocabulary of a system. Once built, continually refined throughout the application's development. They exist to name and model system concepts, specify collaborations, and specify logical database schemas. |
Generated by systems analysts, designers and implementers.
|Object||Shows specific instances and links to others. Created during the analysis and design phase. Illustrates data and object structures and provides specific snapshots of system occurrences. Typically generated by systems analysts, designers, and implementers.|
|Component||Captures the physical structure leading to implementation. Created as part of the architectural process before development, thus the architecture-driven approach. They exist to organize and structure source code, lead the construction of an executable release, and specify a physical database structure. |
Created by the systems architects and programmers.
|Deployment||Captures the actual topology of a system's installation and hardware. They exist to specify the distribution of components and identify system performance bottlenecks. |
Created as part of the architectural process by systems architects, network engineers, and systems engineers.
|Dynamic||Sequence||Captures time-oriented, dynamic behaviors. Represent application's flow controls. Describes what the system does in typical scenarios.|
|Collaboration||Captures message-oriented dynamic behavior. Also represents flow controls, as well as demonstrating the coordinated behavior of the object structures.|
|Statechart||Captures event-oriented behaviors. Can represent the life cycle of the objects as well as their reactive nature. Often used to help model the user interface, as well as devices.|
|Activity||Captures movement-oriented behaviors. Primarily used to model the business workflow, the application's interaction with business work flows, and general operations.|
Today, the term design patterns has achieved buzzword status. Anything and everything must follow a pattern, and everyone has identified his or her architectural design as a pattern. In an article in the Theory and Practice of Object Systems journal, Dirk Riehle and Heinz Zullighoven provide a definition that represents the practices of the pattern community:
A pattern is instructive information that captures the essential structure and insight of a successful family of proven solutions to a recurring problem that arises within a certain context and system of forces.
This definition provides some pointers on how to identify a pattern. As noted by James Coplien in Pattern Languages of Program Design (Addison-Wesley, 1995), a pattern has the following characteristics:
Another way to identify a design pattern is to remember that it is a reoccurring phenomenon and is subject to the rule of three; that is, it can be identified in at least three separate systems or solutions within the same problem domain.
Design patterns can be either generative or non-generative. Generative patterns can be used to solve engineering problems, whereas non-generative patterns are merely observed. In his book The Timeless Way of Building (Oxford University Press, 1970), Christopher Alexander discusses the difference in these terms:
…in one respect they are very different. The patterns in the world merely exist. But the same patterns in our minds are dynamic. They have force. They are generative. They tell us what to do; they tell us how we shall, or may, generate them; and they tell us too, that under certain circumstances, we must create them. Each pattern is a rule which describes what you have to do to generate the entity which it defines.
Fortunately, a number of patterns have already been identified that can be applied to common problems. These patterns typically are defined within a template. Some patterns have been identified for a particular industry segment, and some patterns address specific technical design problems. Although an organization can identify its own patterns for its applications, we recommend first following the patterns that have successfully provided solutions within the software industry. To apply a pattern to an application, the technical and business requirements for the application must first be identified. Then the design pattern that best fits the needs of the application can be selected. (In this respect, design patterns get to the heart of reusability, because they enable entire system architectures to be reused.) A good generative design pattern supplies the rationale for the solution as well as the solution itself. However, most software development design patterns stop at the architectural level and do not suggest specific code implementations.
As with any immature science, disagreement abounds on what should be included in a properly defined pattern. However, several common elements exist between many patterns. The general consensus regarding these elements has been summarized by Brad Appleton in his paper Patterns and Software: Essential Concepts and Terminology (www.enteract.com/~bradapp/docs/patterns-intro.html) and is paraphrased below:
A typical design pattern has a brief abstract that summarizes all these elements and presents a clear picture of the forces and the solution that the pattern addresses.
Generative design patterns provide complete solutions to business and technical problems. They are primarily geared toward "green field" designs, meaning they are applied to new designs. Design antipatterns are geared toward solving problems for which an inadequate solution is already in place. The best way to differentiate patterns and antipatterns is to say:
Patterns lead to an original solution for a set of criteria and forces. Antipatterns lead to a new solution when the current design is not working.
Thus patterns are used when starting from scratch, and antipatterns are used to fix things that are broken. Figure 2.2, which is from William Brown, Raphael Malveau, Hays McCormick III, and Thomas Mowbray's book AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis (John Wiley and Sons, 1998), illustrates this concept.
Figure 2.2 Differences between design pattern and antipatterns
Design patterns and antipatterns are similar in their structure and underlying principles. Thus, antipatterns:
Existing antipattern definitions can be leveraged as solutions, or as the starting point for the process of creating organization-specific antipatterns. They can provide a structured way for new or inexperienced software developers to learn how to solve the complex application development problems they will encounter.