Practice 16. Architect with Components and Services


Bruce MacIsaac

Component and service-oriented architectures help you to build software that is more easily understood, changed, and reused.

Problem

Components are relatively independent units that can be assembled to build larger systems. The benefits of components include the following:

  • An architecture that is more easily understood, as each component defines a cohesive capability and hides internal complexity.

  • A system that can be more easily changed, as individual components can be modified without affecting other components.

  • Greater potential for reuse, as components can be assembled for different uses.

Service-oriented architectures are similar to component architectures, in that systems can be composed using services. Services are published capabilities that can be discovered and used dynamically. Identifying services and designing service providers act as an extension to the basic approach for identifying interfaces and designing components that implement them.

This practice describes how to identify and document components and services.

Background

Let's discuss why components are needed and review the different kinds of components.

The Need for Components and Services

Early on in my career as a software developer, I was asked to review some code. Sadly this was not because of my great expertise, but because a quality requirement box needed to be checked, and nobody else was available.

This code was part of a highly successful commercial product, critical to the company. I looked forward to learning how professionals designed software. My first disillusionment was that there was little design documentation, and it was out of date. "Look at the code" was the recommendation. After studying the code for several hours, I went to a senior developer and expressed my confusion. "I don't understand how this software is organized. There are lots of modules, but I don't understand the grouping." His response was that the low-level modules had become numerous over time and so were grouped alphabetically. Any module could invoke any other. Large data structures were passed around or shared by different modules, and it was difficult to determine what data each module used. The result was a system that was very difficult to understand. Fixing bugs in one module often introduced new bugs as side-effects, because the dependencies between modules were not clear.[10]

[10] I wish I could say that I learned the code, recommended key improvements, and saved the day. Unfortunately, I was unable to figure out the big picture, and my review added little value.

Eventually, that system was completely reengineered, at great expense. Instead of having lots of small modules organized alphabetically, components with specific responsibilities were organized into subsystems and layers. Instead of large shared data structures, interfaces explicitly defined inputs and outputs. Instead of minor changes and bug fixes having broad impact across the system, most changes could be isolated to a single component, while other code, which depended only on the interfaces to the component, was unaffected. From this and other experiences with both good and bad architectures, I have learned an important lesson: components that hide complexity behind interfaces are the key to creating understandable systems.

Components that hide complexity make systems understandable.


Definition of Component, Service, and Subsystem[11]

[11] These "kinds of components" are abstracted from RUP 7.0.

The term "component" has been overused. It is often used generally to mean "any constituent part." RUP defines it much more narrowly as "a non-trivial, nearly independent, and replaceable part of a system that fulfills a clear function in the context of a well-defined architecture."

The key term is "replaceability." Anyone who has upgraded a computer or repaired a car understands the basic principles of component parts: there are connection points that have to fit, and the part has to meet certain specifications. These specifications allow you to replace an old part with a new one, even one built by a different manufacturer.

A component's specification allows it to be replaced.


The most basic form of replaceability is design and implementation replaceability. The component is a "black box," exposing a set of interfaces. Since clients depend only on these interfaces, the underlying component can be modified or replaced, provided it continues to support the specified interfaces.

A "service," in simplified terms, is a capability that is published to a directory so that clients can dynamically discover its interface. This allows flexible composition of new capabilities from loosely coupled services. A "service component" implements one or more services much as a regular component implements interfaces. The difference is that services are dynamically discoverable.

Services are published capabilities that can be dynamically discovered and composed.


In this practice the term "component" applies equally to service components.

Note the term "design subsystem" is used interchangeably with "design component," reflecting the evolution of these terms; once different, they have evolved in the latest UML 2.0 specification to be essentially equivalent, with "subsystem" generally used to refer to larger granularity components.

Applying the Practice

This practice provides an overview of how to identify and specify components.

Identify Components and Services from the Problem Space

Components can often be discovered from the problem that the software is intended to solve. The largest components (often called subsystems) or services can often be identified by modeling the overall business and encapsulating business rules or parts of the business process.[12]

[12] Modeling of business processes and business rules is covered by the Business Modeling discipline in RUP.

Components encapsulate concepts in the problem space.


To identify components within an application, start by identifying the "things in the system which have responsibilities and behavior." Nouns used in the requirements are a good starting point, as are domain objects (see Practice 8: Understand the Domain). A more structured approach[13] is to identify "analysis classes," including the following:

[13] This is the approach of "analysis modeling" found in RUP.

  • Boundary classes that encapsulate interactions with an external system, device, or human actor.

  • Entity classes encapsulating information that must be stored, and the business rules associated with that stored information.

  • Control classes to coordinate behavior.

These analysis classes provide a key starting point for components. Start by grouping highly interdependent or closely related classes into components, and separate classes that are relatively independent.

Identify Components and Services from Existing Assets

Often, there are existing company resources, such as databases or existing systems. Encapsulating resources with a standard interface allows the resource to be accessed in a consistent manner and enables it to be replaced or modified as needed.

Components encapsulate resources.


Use a Layered Architecture

Components should encapsulate functionality, but they should also be able to take advantage of common services. A layered architecture classifies functionality into layers in such a way that upper layers use services from lower layers. This classification clarifies the dependencies and responsibilities of components. It also helps identify reusable components, as functionality that logically belongs to a lower layer can be refactored into a component at that lower layer.

Separating concerns with a layered architecture makes it easier to identify components.


Figure 6.6 shows a typical layering approach.

Figure 6.6. A Typical Layering Approach.

The separation of concerns in each layer makes it easier to identify components and make changes. (From RUP v7.0.)


Notice how this layered approach isolates hardware and operating system dependencies to the lowest layer, making it easier for the upper layers to migrate to other platforms.

Layering is equally applicable to service-oriented architectures. Although services discussions often focus on the peer-to-peer interactions within the top layers of the architecture, services can still benefit from layering to increase reuse and reduce dependencies.[14] And even when the services themselves are not layered, the components that implement services can usually benefit from a layered architecture.

[14] See Stojanovic 2005 and Greenfield 2004 for examples of layering applied to service-oriented architectures.

Factor Out Common Behavior

Several classes may have similar requirements that can be factored out and moved into another component. In particular, mechanisms such as persistent storage, interprocess communication, security, transactions, error reporting, format conversion, and so on are natural components.

Look at design patterns with similar challenges (see Practice 15: Leverage Patterns). Patterns often suggest how class responsibilities can be refactored to reduce coupling in ways that create better component boundaries.

Looking more broadly, consider behavior that may be reusable for other systems in the future and try to isolate such behavior into components.

Isolate reusable behavior into components.


Consider Likely Changes

Stakeholders often have ideas about what they would like the system to do in the future. For example, they may wish a product to be configurable to support other languages, other platforms, or different working environments. You should not expend a lot of effort designing for requirements that may never materialize. However, isolating potential areas of change can be an inexpensive means of enabling easier changes in the future. For example, the simple layering approach described earlier shows how to isolate platform dependencies to ease future migration.

Isolating potential areas of change enables future changes.


Separate Specification and Realization

UML provides for the separation of a component's design into specification and realization. The specification serves as a contract that defines everything a client needs to know to use the component. The realization is the detailed internal design intended to guide the implementer. You can even create two or more "realization" components for one specification component if you wish to have different implementations.[15] A similar approach is used to specify services separately from services components.

[15] A component specification can have a different implementation when, for example, different companies provide competing implementations for the same specification, or when components are implemented and optimized for specific platforms.

Figure 6.7 shows how an engine can be specified. The provided interface (shown as a circle) is a powertrain. The required interface (a half circle) is a power source or fuel. An engine can be reused in a car or boat, provided the right connections are present. The realizations of the engine specification are not shown in this diagram, but would be actual engines provided by various engine manufacturers.

Figure 6.7. Example of Connected Components.

An engine can be used in a car or a boat, provided it has the right connections. (From the OMG UML 2.0 specification.)


Figure 6.8 gives a software example based on Java Database Connectivity (JDBC). JDBC is a component that has two connections. The first provides a standard interface for client applications to access a database. The second is a driver interface that the database vendor must realize.

Figure 6.8. Example of Connected Software Components.


These examples show that components both provide and require interfaces, and that the interfaces allow one component to be replaced by another compatible component.

Provided and required interfaces allow components to be replaced.


Specifications often hide the internal complexity of a component. So while the design of a component realization can include a detailed description of interactions between internal elementsusing structure, state, activity, and interaction diagramsspecifications are usually simpler. Specifications usually focus on describing the interfaces, dependencies on other interfaces, and required quality of service. Usually a UML structure diagram, accompanied by text, is sufficient. However, you can use the rich UML notation to provide a rich and precise visual description for the specification. Such precise descriptions tend to be most useful in the following cases:

  • Components that will have a complex externally visible state and behavior.

  • Components that are intended for reuse.

  • Components that will be developed by a separate organization.

  • Components that will have multiple implementations for the same specification.

Services and service components frequently have one or more of the above characteristics, and so benefit from a precise UML specification.

UML can be used to precisely specify components and services.

Provide the Right Level of Interconnection

Some components encapsulate resources or capabilities that need to be accessed by multiple distributed applications. These are natural candidates for wrapping as a service, because service frameworks generally provide a standard and flexible means of managing distributed capabilities. Because accessing a distributed capability involves sending and receiving data over the network, performance and scalability are significant concerns. Message-based asynchronous protocols are often the best choice, as these allow the requestor and provider of a service to work in parallel, rather than await a response from the other.

Not all components should be exposed as services, as the flexibility provided by services comes with a cost. Distribution needs to be administered, interfaces are complicated by the need to minimize the number of message interactions, and asynchronous messaging and parallel processing can be tricky. Most smaller components do not need this complexity.

Not all components should be exposed as services.


Develop Components Iteratively

It can be difficult and costly to refactor components after they have been fully implemented. To validate the design of components and their interfaces, implement basic scenarios that prove the integration of the components and interfaces. Add functionality in each iteration to refine and expand upon the interfaces. Developing components iteratively enables you to find problems, such as coupling or performance issues. You can then correct these problems through refactoring, without impacting a lot of code. This issue is discussed in more detail in Practice 2: Execute Your Project in Iterations and in Practice 10: Prioritize Requirements for Implementation.

Develop components iteratively to enable inexpensive refactoring.


Other Methods

Traditional waterfall development can be used to design and implement components. However, this method can lead to late discovery of problems, and it is difficult and costly to refactor components after they have been fully implemented. The iterative approach validates that components integrate and perform well using partial implementations, and even stubs. The component can then be refactored as necessary without impacting a lot of code.

In an XP approach, components are not a focus. Some can be expected to emerge through refactoring and general good design. The Unified Process explicitly focuses on architecture, and it recommends identifying key components and interfaces and initially implementing just enough to validate architectural decisions. Whereas the Unified Process considers components useful for organizing teams, especially on larger projects, XP recommends a flat organization in which all code is shared.

The Unified Process recommends implementing just enough to validate architectural decisions.


Unlike XP, RUP also extends to cover the needs of larger projects, enterprise reuse, and enterprise architecture by describing how and when to use detailed component specifications. RUP also provides technology-specific information, such as how to design and implement services using J2EE and IBM Rational Software Architect.

Levels of Adoption

This practice can be adopted at different levels:

  • Basic. Understanding components and services. It is important for developers to know how components and services can be applied to the systems they develop, and to understand the heuristics for good design, including principles of cohesion, coupling, and information hiding. Understanding and applying good design principles can speed development and enhance communication.

  • Intermediate. Use analysis models and collaborations to discover components.

    Modeling can be applied as needed, in an agile fashion, or as a concerted effort of up-front modeling suited to more formal projects. In both cases, early identification of components, services, and their interfaces helps to stabilize the software early and allows developers to work more independently, thus increasing the team's ability to deliver iteratively.

  • Advanced. Create detailed component and service behavior specifications to enable completely independent and even multiple implementations.

    Detailed specification requires investment, training, and discipline. Such investments are usually associated with large projects, or enterprise-level reuse and architecture efforts.

Related Practices

  • Practice 2: Execute Your Project in Iterations explains that component interactions should be identified and validated in early iterations using partial implementations, allowing you to make corrections inexpensively.

  • Practice 11: Leverage Legacy Systems describes how to maximize the benefits of existing legacy systems, including how to incorporate them as components and services in larger enterprise systems.

  • Practice 13: Organize Around the Architecture describes how teams are organized around the components in the architecture.

  • Practice 15: Leverage Patterns describes how to use patterns as tried and true solutions. Patterns can help you to identify and refactor components. You can create your own patterns to show how your components should be used.

  • Practice 17: Actively Promote Reuse includes components as key reusable assets that should be a part of any systematic reuse effort.

  • Practice 18: Model Key Perspectives describes how visualizing software aids understanding. Visualizing dependencies and interactions can help you identify, understand, and evaluate components.

In general, good practices lead to more effective component development, which in turn leads to more effective overall system development.

Additional Information

Information in the Unified Process

OpenUP/Basic describes how to design and develop component-based systems. RUP includes more advanced techniques, such as how to apply business modeling and systems engineering techniques to identify large-scale components and how to build systems of systems using techniques such as use-case flow down. RUP and RUP plug-ins also provide technology and tool-specific guidance. For example, the service-oriented architecture plug-in to RUP includes a UML profile for service-oriented architectures and guidelines for designing and developing services using IBM Rational Software Architect.

Additional Reading

For guidance on component identification and design, see the Additional Reading section in Practice 15: Leverage Patterns (component identification and design are key concerns of many architectural patterns).

For a good overall reference for object-oriented analysis and design and component-based development, see the following:

Jim Arlow and Ila Neustadt. UML and the Unified Process: Practical Object-Oriented Analysis and Design. Addison-Wesley, 2002.

For discussion of components from the large to the small, and for guidance on adopting component-based development at an enterprise scale, see the following:

Peter Herzum and Oliver Sims. Business Component Factory: A Comprehensive Overview of Component-Based Development for the Enterprise. John Wiley & Sons, 1999.

For guidance on creating detailed component specifications[16] see the following:

[16] The authors do not use standard UML, and the work predates UML 2.0, nevertheless, the guidelines and examples are worth reading.

John Cheesman and John Daniels. UML Components: A Simple Process for Specifying Component-Based Software. Addison-Wesley, 2000.

For some up-to-date examples for modeling components using UML 2.0:

Scott W. Ambler. The Object Primer: Agile Model-Driven Development with UML 2.0. Cambridge University Press, 2004.

The following is an excellent article on how to move beyond abstract theory and practically implement replaceability:

Fredrik Ferm. "The What, Why, and How of a Subsystem." The Rational Edge, 2003. Found online at http://www.ibm.com/developerworks.

For a description of service-oriented architectures and their relationship to components, see the following:

Dirk Krafzig, Karl Banke, and Dirk Slama. Enterprise SOA: Service-Oriented Architecture Best Practices. Prentice Hall, 2005.

Zoran Stojanovic and Ajantha Dahanayake, eds. Service-Oriented Software System Engineering: Challenges and Practices. Idea Group Publishing, 2005.



Agility and Discipline Made Easy(c) Practices from OpenUP and RUP
Agility and Discipline Made Easy: Practices from OpenUP and RUP
ISBN: 0321321308
EAN: 2147483647
Year: 2006
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net