Object-oriented frameworks help reduce the cost and improve the quality of networked applications by reifying software designs and pattern languages that have proven effective in particular application domains. This chapter illustrates what frameworks are and compares them with other popular software development techniques, such as class libraries, components, patterns, and model-integrated computing. It then illustrates the process of applying frameworks to networked applications and outlines the ACE frameworks that are the focus of this book. These frameworks are based on a pattern language [POSA1, POSA2] that has been applied to thousands of production networked applications and middleware worldwide.
1.1 An Overview of Object-Oriented Frameworks
Even as computing power and network bandwidth increase dramatically, the development of networked application software remains expensive, time consuming, and error prone. The cost and effort stems from the growing demands placed on networked software, as well as the continual rediscovery and reinvention of core software design and implementation artifacts throughout the software industry. Moreover, the heterogeneity of hardware architectures, diversity of OS and network platforms, and stiff global competition makes it increasingly hard to build high-quality networked application software from scratch.
The key to building high-quality networked software in a time-to-market-driven environment is the ability to reuse successful software designs and implementations that have already been developed. Reuse has been a popular topic of debate and discussion for over 30 years in the software community [McI68]. There are two general types of reuse:
Opportunistic reuse, in which developers cut and paste code from existing programs to create new ones. Opportunistic reuse works in a limited way for individual programmers or small groups. It doesn't scale up across business units or enterprises, however, and therefore doesn't significantly reduce development cycle time and cost or improve software quality. Worse, opportunistic reuse can actually impede development progress since cut-and-paste code often begins to diverge as it proliferates, forcing developers to fix the same bugs multiple times in multiple places.
Systematic reuse, which is an intentional and concerted effort to create and apply multiuse software architectures, patterns, frameworks, and components throughout a product line [CN02]. In a well-honed systematic reuse process, each new project leverages time-proven designs and implementations, only adding new code that's spe-cific to a particular application. This type of reuse is essential to increase software productivity and quality by breaking the costly cycle of rediscovering, reinventing, and revalidating common software artifacts.
Middleware [SS02] is a class of software that can increase systematic reuse levels significantly by functionally bridging the gap between the end-to-end functional requirements of networked applications and the underlying operating systems and network protocol stacks. Middleware provides capabilities that are critical to networked applications because they automate common network programming tasks. Developers who use mid-dleware can therefore program their networked applications more like stand-alone applications, rather than wrestling with the many tedious and error-prone details associated with low-level OS event demultiplexing, message buffering and queueing, marshaling and de-marshaling, and connection management mechanisms. Popular examples of middleware include Java virtual machines (JVMs), Enterprise JavaBeans (EJB), .NET, the Common Object Request Broker Architecture (CORBA), and the ADAPTIVE Communication Environment (ACE).
Systematically developing high-quality, reusable middleware for networked applications presents many hard technical challenges, including
Detecting and recovering from transient and partial failures of networks and hosts in an application-independent manner
Minimizing the impact of latency and jitter on end-to-end application performance
Determining how to partition a distributed application into separate component services
Deciding where and when to distribute and load balance services in a network
Since reusable middleware is inherently abstract, it's hard to validate its quality and to manage its production. Moreover, the skills required to develop, deploy, and support reusable networked application middleware have traditionally been a "black art," locked in the heads of expert developers and architects. These technical impediments to systematic reuse are often exacerbated by a myriad of nontechnical impediments [Hol97], such as organizational, economic, administrative, political, sociological, and psychological factors. It's therefore not surprising that significant levels of software reuse have been slow to materialize in many projects and organizations [Sch00].
While it's never easy to make reuse work universally, we've led the development of powerful host infrastructure middleware called ACE that's designed specifically with systematic reuse in mind. During the past decade, we've written hundreds of thousands of lines of C++ code while developing and applying ACE to networked applications as part of our work with dozens of telecommunication, aerospace, medical, and financial services companies. As a result of our experience, we've documented many patterns and pattern languages [POSA2, POS00] that have guided the design of reuseable middleware and applications. In addition, we've taught hundreds of tutorials and courses on reuse, middle-ware, and patterns to thousands of developers and students. Despite the many technical and nontechnical challenges, we've identified a solid body of work that combines advanced research, time-proven design knowledge, hands-on experience, and software artifacts that can significantly enhance the systematic reuse of networked application software.
At the heart of this body of work are object-oriented frameworks [FJS99b, FJS99a], which are a powerful technology for achieving systematic reuse of networked application software.1 Below, we describe the three characteristics of frameworks [JF88] that help them to achieve the important networked application qualities listed on page xi. Figure 1.1 (page 4) illustrates how these characteristics work together.
Figure 1.1: Synergy of Framework Capabilities
A framework provides an integrated set of domain-specific structures and functionality. Systematic reuse of software depends largely on how well frameworks model the commonalities and variabilities [CHW98] in application domains, such as business data processing, telecom call processing, graphical user interfaces, or distributed object computing middleware. Since frameworks reify the key roles and relationships of classes in application domains, the amount of reusable code increases and the amount of code rewritten for each application decreases.
A framework exhibits "inversion of control" at run time via callbacks. A callback is an object registered with a dispatcher that calls back to a method on the object when a particular event occurs, such as a connection request or data arriving on a socket handle. Inversion of control decouples the canonical detection, demultiplexing, and dispatching steps within a framework from the application-defined event handlers managed by the framework. When events occur, the framework calls back to virtual hook methods in the registered event handlers, which then perform application-defined processing in response to the events.
Since frameworks exhibit inversion of control, they can simplify application design because the frameworkrather than the applicationruns the event loop to detect events, demultiplex events to event handlers, and dispatch hook methods on the handlers that process the events. The use of virtual hook methods in the handler classes decouples the application's classes from the framework, allowing each to be changed independently as long as the interface signature and interaction protocols aren't modified.
A framework is a "semi-complete" application that programmers can customize to form complete applications by inheriting from and instantiating classes in the framework. Inheritance enables the features of framework base classes to be shared selectively by subclasses. If a base class provides default implementations of its methods, application developers need only override those virtual methods whose default behavior doesn't meet their needs.
Since a framework is a semi-complete application, it enables larger-scale reuse of software than can be achieved by reusing individual classes or stand-alone functions. The amount of reuse increases due to a framework's ability to integrate application-defined and application-independent classes. In particular, a framework abstracts the canonical control flow of applications in a domain into families of related classes, which can collaborate to integrate customizable application-independent code with customized application-defined code.