1
What Is Software Architecture?
We are called to be architects of the future, not its victims.
—R. Buckminster Fuller
Writing (on our part) and reading (on your part) a book about software
architecture, which distills the experience of many people, presupposes
that
1. having a reasonable software architecture is important to the
successful development of a software system and
2. there is a sufficient body of knowledge about software architecture
to fill up a book.
There was a time when both of these assumptions needed justification.
Early editions of this book tried to convince readers that both of these
assumptions are true and, once you were convinced, supply you with
basic knowledge so that you could apply the practice of architecture
yourself. Today, there seems to be little controversy about either aim,
and so this book is more about the supplying than the convincing.
The basic principle of software architecture is every software system
is constructed to satisfy an organization’s business goals, and that the
architecture of a system is a bridge between those (often abstract)
business goals and the final (concrete) resulting system. While the path
from abstract goals to concrete systems can be complex, the good news
is that software architectures can be designed, analyzed, and documented
using known techniques that will support the achievement of these
business goals. The complexity can be tamed, made tractable.
These, then, are the topics for this book: the design, analysis, and
documentation of architectures. We will also examine the influences,
principally in the form of business goals that lead to quality attribute
requirements, that inform these activities.
In this chapter, we will focus on architecture strictly from a software
engineering point of view. That is, we will explore the value that a
software architecture brings to a development project. Later chapters
will take business and organizational perspectives.
1.1 What Software Architecture Is and What It Isn’t
There are many definitions of software architecture, easily discoverable
with a web search, but the one we like is this:
The software architecture of a system is the set of structures needed to
reason about the system. These structures comprise software elements,
relations among them, and properties of both.
This definition stands in contrast to other definitions that talk about
the system’s “early” or “major” or “important” decisions. While it is true
that many architectural decisions are made early, not all are—especially
in Agile and spiral-development projects. It’s also true that many
decisions that are made early are not what we would consider
architectural. Also, it’s hard to look at a decision and tell whether it’s
“major.” Sometimes only time will tell. And since deciding on an
architecture is one of the architect’s most important obligations, we need
to know which decisions an architecture comprises.
Structures, by contrast, are fairly easy to identify in software, and they
form a powerful tool for system design and analysis.
So, there we are: Architecture is about reasoning-enabling structures.
Let’s look at some of the implications of our definition.
Architecture Is a Set of Software Structures
This is the first and most obvious implication of our definition. A
structure is simply a set of elements held together by a relation. Software
systems are composed of many structures, and no single structure can lay
claim to being the architecture. Structures can be grouped into
categories, and the categories themselves provide useful ways to think
about the architecture. Architectural structures can be organized into
three useful categories, which will play an important role in the design,
documentation, and analysis of architectures:
1. Component-and-connector structures
2. Module structures
3. Allocation structures
We’ll delve more into these types of structures in the next section.
Although software comprises an endless supply of structures, not all
of them are architectural. For example, the set of lines of source code
that contain the letter “z,” ordered by increasing length from shortest to
longest, is a software structure. But it’s not a very interesting one, nor is
it architectural. A structure is architectural if it supports reasoning about
the system and the system’s properties. The reasoning should be about
an attribute of the system that is important to some stakeholder(s). These
include properties such as the functionality achieved by the system, the
system’s ability to keep operating usefully in the face of faults or
attempts to take it down, the ease or difficulty of making specific
changes to the system, the system’s responsiveness to user requests, and
many others. We will spend a great deal of time in this book exploring
the relationship between architecture and quality attributes like these.
Thus the set of architectural structures is neither fixed nor limited.
What is architectural depends on what is useful to reason about in your
context for your system.
Architecture Is an Abstraction
Since architecture consists of structures, and structures consist of
elements1 and relations, it follows that an architecture comprises
software elements and how those elements relate to each other. This
means that architecture specifically and intentionally omits certain
information about elements that is not useful for reasoning about the
system. Thus an architecture is foremost an abstraction of a system that
selects certain details and suppresses others. In all modern systems,
elements interact with each other by means of interfaces that partition
details about an element into public and private parts. Architecture is
concerned with the public side of this division; private details of
elements—details having to do solely with internal implementation—are
not architectural. This abstraction is essential to taming the complexity
of an architecture: We simply cannot, and do not want to, deal with all of
the complexity all of the time. We want—and need—the understanding
of a system’s architecture to be many orders of magnitude easier than
understanding every detail about that system. You can’t keep every detail
of a system of even modest size in your head; the point of architecture is
to make it so you don’t have to.
1. In this book, we use the term “element” when we mean either a
module or a component, and don’t want to distinguish between the
two.
Architecture versus Design
Architecture is design, but not all design is architecture. That is, many
design decisions are left unbound by the architecture—it is, after all, an
abstraction—and depend on the discretion and good judgment of
downstream designers and even implementers.
Every Software System Has a Software Architecture
Every system has an architecture, because every system has elements
and relations. However, it does not follow that the architecture is known
to anyone. Perhaps all of the people who designed the system are long
gone, the documentation has vanished (or was never produced), the
source code has been lost (or was never delivered), and all we have at
hand is the executing binary code. This reveals the difference between
the architecture of a system and the representation of that architecture.
Given that an architecture can exist independently of its description or
specification, this raises the importance of architecture documentation,
which is described in Chapter 22.
Not All Architectures Are Good Architectures
Our definition is indifferent as to whether the architecture for a system is
a good one or a bad one. An architecture may either support or hinder
achieving the important requirements for a system. Assuming that we do
not accept trial and error as the best way to choose an architecture for a
system—that is, picking an architecture at random, building the system
from it, and then hacking away and hoping for the best—this raises the
importance of architecture design, which is treated in Chapter 20 and
architecture evaluation, which will be dealt with in Chapter 21.
Architecture Includes Behavior
The behavior of each element is part of the architecture insofar as that
behavior can help you reason about the system. The behavior of elements
embodies how they interact with each other and with the environment.
This is clearly part of our definition of architecture and will have an
effect on the properties exhibited by the system, such as its runtime
performance.
Some aspects of behavior are below the architect’s level of concern.
Nevertheless, to the extent that an element’s behavior influences the
acceptability of the system as a whole, this behavior must be considered
part of the system’s architectural design, and should be documented as
such.
System and Enterprise Architectures
Two disciplines related to software architecture are system
architecture and enterprise architecture. Both of these disciplines
have broader concerns than software and affect software
architecture through the establishment of constraints within which a
software system, and its architect, must live.
System Architecture
A system’s architecture is a representation of a system in which
there is a mapping of functionality onto hardware and software
components, a mapping of the software architecture onto the
hardware architecture, and a concern for the human interaction with
these components. That is, system architecture is concerned with
the totality of hardware, software, and humans.
A system architecture will influence, for example, the
functionality that is assigned to different processors and the types
of networks that connect those processors. The software
architecture will determine how this functionality is structured and
how the software programs residing on the various processors
interact.
A description of the software architecture, as it is mapped to
hardware and networking components, allows reasoning about
qualities such as performance and reliability. A description of the
system architecture will allow reasoning about additional qualities
such as power consumption, weight, and physical dimensions.
When designing a particular system, there is frequently
negotiation between the system architect and the software architect
over the distribution of functionality and, consequently, the
constraints placed on the software architecture.
Enterprise Architecture
Enterprise architecture is a description of the structure and behavior
of an organization’s processes, information flow, personnel, and
organizational subunits. An enterprise architecture need not include
computerized information systems—clearly, organizations had
architectures that fit the preceding definition prior to the advent of
computers—but these days enterprise architectures for all but the
smallest businesses are unthinkable without information system
support. Thus a modern enterprise architecture is concerned with
how software systems support the enterprise’s business processes
and goals. Typically included in this set of concerns is a process for
deciding which systems with which functionality the enterprise
should support.
An enterprise architecture will specify, for example, the data
model that various systems use to interact. It will also specify rules
for how the enterprise’s systems interact with external systems.
Software is only one concern of enterprise architecture. How the
software is used by humans to perform business processes and the
standards that determine the computational environment are two
other common concerns addressed by enterprise architecture.
Sometimes the software infrastructure that supports
communication among systems and with the external world is
considered a portion of the enterprise architecture; at other times,
this infrastructure is considered one of the systems within an
enterprise. (In either case, the architecture of that infrastructure is a
software architecture!) These two views will result in different
management structures and spheres of influence for the individuals
concerned with the infrastructure.
Are These Disciplines in Scope for This Book? Yes! (Well, No.)
The system and the enterprise provide environments for, and
constraints on, the software architecture. The software architecture
must live within the system and the enterprise, and increasingly is
the focus for achieving the organization’s business goals. Enterprise
and system architectures share a great deal with software
architectures. All can be designed, evaluated, and documented; all
answer to requirements; all are intended to satisfy stakeholders; all
consist of structures, which in turn consist of elements and
relationships; all have a repertoire of patterns at their respective
architects’ disposal; and the list goes on. So to the extent that these
architectures share commonalities with software architecture, they
are in the scope of this book. But like all technical disciplines, each
has its own specialized vocabulary and techniques, and we won’t
cover those. Copious other sources exist that do.
1.2 Architectural Structures and Views
Because architectural structures are at the heart of our definition and
treatment of software architecture, this section will explore these
concepts in more depth. These concepts are dealt with in much greater
depth in Chapter 22, where we discuss architecture documentation.
Architectural structures have counterparts in nature. For example, the
neurologist, the orthopedist, the hematologist, and the dermatologist all
have different views of the various structures of a human body, as
illustrated in Figure 1.1. Ophthalmologists, cardiologists, and podiatrists
concentrate on specific subsystems. Kinesiologists and psychiatrists are
concerned with different aspects of the entire arrangement’s behavior.
Although these views are pictured differently and have very different
properties, all are inherently related and interconnected: Together they
describe the architecture of the human body.
Figure 1.1 Physiological structures
Architectural structures also have counterparts in human endeavors.
For example, electricians, plumbers, heating and air conditioning
specialists, roofers, and framers are each concerned with different
structures in a building. You can readily see the qualities that are the
focus of each of these structures.
So it is with software.
Three Kinds of Structures
Architectural structures can be divided into three major categories,
depending on the broad nature of the elements they show and the kinds
of reasoning they support:
1. Component-and-connector (C&C) structures focus on the way the
elements interact with each other at runtime to carry out the
system’s functions. They describe how the system is structured as a
set of elements that have runtime behavior (components) and
interactions (connectors). Components are the principal units of
computation and could be services, peers, clients, servers, filters,
or many other types of runtime element. Connectors are the
communication vehicles among components, such as call-return,
process synchronization operators, pipes, or others. C&C
structures help answer questions such as the following:
What are the major executing components and how do they
interact at runtime?
What are the major shared data stores?
Which parts of the system are replicated?
How does data progress through the system?
Which parts of the system can run in parallel?
Can the system’s structure change as it executes and, if so,
how?
By extension, these structures are crucially important for asking
questions about the system’s runtime properties, such as
performance, security, availability, and more.
C&C structures are the most common ones that we see, but two
other categories of structures are important and should not be
overlooked.
Figure 1.2 shows a sketch of a C&C structure of a system using an
informal notation that is explained in the figure’s key. The system
contains a shared repository that is accessed by servers and an
administrative component. A set of client tellers can interact with
the account servers and communicate among themselves using a
publish-subscribe connector.
Figure 1.2 A component-and-connector structure
2. Module structures partition systems into implementation units,
which in this book we call modules. Module structures show how a
system is structured as a set of code or data units that have to be
constructed or procured. Modules are assigned specific
computational responsibilities and are the basis of work
assignments for programming teams. In any module structure, the
elements are modules of some kind (perhaps classes, packages,
layers, or merely divisions of functionality, all of which are units
of implementation). Modules represent a static way of considering
the system. Modules are assigned areas of functional
responsibility; there is less emphasis in these structures on how the
resulting software manifests itself at runtime. Module
implementations include packages, classes, and layers. Relations
among modules in a module structure include uses, generalization
(or “is-a”), and “is part of.” Figures 1.3 and 1.4 show examples of
module elements and relations, respectively, using the Unified
Modeling Language (UML) notation.
Figure 1.3 Module elements in UML
Figure 1.4 Module relations in UML
Module structures allow us to answer questions such as the
following:
What is the primary functional responsibility assigned to each
module?
What other software elements is a module allowed to use?
What other software does it actually use and depend on?
What modules are related to other modules by generalization
or specialization (i.e., inheritance) relationships?
Module structures convey this information directly, but they can
also be used to answer questions about the impact on the system
when the responsibilities assigned to each module change. Thus
module structures are the primary tools for reasoning about a
system’s modifiability.
3. Allocation structures establish the mapping from software
structures to the system’s nonsoftware structures, such as its
organization, or its development, test, and execution environments.
Allocation structures answer questions such as the following:
Which processor(s) does each software element execute on?
In which directories or files is each element stored during
development, testing, and system building?
What is the assignment of each software element to
development teams?
Some Useful Module Structures
Useful module structures include:
Decomposition structure. The units are modules that are related to
each other by the “is-a-submodule-of” relation, showing how
modules are decomposed into smaller modules recursively until the
modules are small enough to be easily understood. Modules in this
structure represent a common starting point for design, as the
architect enumerates what the units of software will have to do and
assigns each item to a module for subsequent (more detailed) design
and eventual implementation. Modules often have products (such as
interface specifications, code, and test plans) associated with them.
The decomposition structure determines, to a large degree, the
system’s modifiability. That is, do changes fall within the purview of
a few (preferably small) modules? This structure is often used as the
basis for the development project’s organization, including the
structure of the documentation, and the project’s integration and test
plans. Figure 1.5 shows an example of a decomposition structure.
Figure 1.5 A decomposition structure
Uses structure. In this important but often overlooked structure, the
units are also modules, and perhaps classes. The units are related by
the uses relation, a specialized form of dependency. One unit of
software uses another if the correctness of the first requires the
presence of a correctly functioning version (as opposed to a stub) of
the second. The uses structure is used to engineer systems that can
be extended to add functionality, or from which useful functional
subsets can be extracted. The ability to easily create a subset of a
system allows for incremental development. This structure is also
the basis for measuring social debt—the amount of communication
that actually is, as opposed to merely should be, taking place among
teams—as it defines which teams should be talking to each other.
Figure 1.6 shows a uses structure and highlights the modules that
must be present in an increment if the module admin.client is
present.
Figure 1.6 Uses structure
Layer structure. The modules in this structure are called layers. A
layer is an abstract “virtual machine” that provides a cohesive set of
services through a managed interface. Layers are allowed to use
other layers in a managed fashion; in strictly layered systems, a layer
is only allowed to use a single other layer. This structure imbues a
system with portability—that is, the ability to change the underlying
virtual machine. Figure 1.7 shows a layer structure of the UNIX
System V operating system.
Figure 1.7 Layer structure
Class (or generalization) structure. The modules in this structure are
called classes, and they are related through an “inherits-from” or “is-
an-instance-of” relation. This view supports reasoning about
collections of similar behavior or capability and parameterized
differences. The class structure allows one to reason about reuse and
the incremental addition of functionality. If any documentation exists
for a project that has followed an object-oriented analysis and design
process, it is typically this structure. Figure 1.8 shows a
generalization structure taken from an architectural expert tool.
Figure 1.8 Generalization structure
Data model. The data model describes the static information
structure in terms of data entities and their relationships. For
example, in a banking system, entities will typically include
Account, Customer, and Loan. Account has several attributes, such
as account number, type (savings or checking), status, and current
balance. A relationship may dictate that one customer can have one
or more accounts, and one account is associated with one or more
customers. Figure 1.9 shows an example of a data model.
Figure 1.9 Data model
Some Useful C&C Structures
C&C structures show a runtime view of the system. In these structures,
the modules just described have all been compiled into executable forms.
Thus all C&C structures are orthogonal to the module-based structures
and deal with the dynamic aspects of a running system. For example, one
code unit (module) could be compiled into a single service that is
replicated thousands of times in an execution environment. Or 1,000
modules can be compiled and linked together to produce a single runtime
executable (component).
The relation in all C&C structures is attachment, showing how the
components and the connectors are hooked together. (The connectors
themselves can be familiar constructs such as “invokes.”) Useful C&C
structures include:
Service structure. The units here are services that interoperate
through a service coordination mechanism, such as messages. The
service structure is an important structure to help engineer a system
composed of components that may have been developed
independently of each other.
Concurrency structure. This C&C structure allows the architect to
determine opportunities for parallelism and the locations where
resource contention may occur. The units are components, and the
connectors are their communication mechanisms. The components
are arranged into “logical threads.” A logical thread is a sequence of
computations that could be allocated to a separate physical thread
later in the design process. The concurrency structure is used early in
the design process to identify and manage issues associated with
concurrent execution.
Some Useful Allocation Structures
Allocation structures define how the elements from C&C or module
structures map onto things that are not software—typically hardware
(possibly virtualized), teams, and file systems. Useful allocation
structures include:
Deployment structure. The deployment structure shows how
software is assigned to hardware processing and communication
elements. The elements are software elements (usually a process
from a C&C structure), hardware entities (processors), and
communication pathways. Relations are “allocated-to,” showing on
which physical units the software elements reside, and “migrates-
to,” if the allocation is dynamic. This structure can be used to reason
about performance, data integrity, security, and availability. It is of
particular interest in distributed systems and is the key structure
involved in the achievement of the quality attribute of deployability
(see Chapter 5). Figure 1.10 shows a simple deployment structure in
UML.
Figure 1.10 Deployment structure
Implementation structure. This structure shows how software
elements (usually modules) are mapped to the file structures in the
system’s development, integration, test, or configuration control
environments. This is critical for the management of development
activities and build processes.
Work assignment structure. This structure assigns responsibility for
implementing and integrating the modules to the teams that will
carry out these tasks. Having a work assignment structure be part of
the architecture makes it clear that the decision about who does the
work has architectural as well as management implications. The
architect will know the expertise required on each team. Amazon’s
decision to devote a single team to each of its microservices, for
example, is a statement about its work assignment structure. On
large development projects, it is useful to identify units of functional
commonality and assign those to a single team, rather than having
them be implemented by everyone who needs them. This structure
will also determine the major communication pathways among the
teams: regular web conferences, wikis, email lists, and so forth.
Table 1.1 summarizes these structures. It lists the meaning of the
elements and relations in each structure and tells what each might be
used for.
Table 1.1 Useful Architectural Structures
Soft Eleme Relations Useful for Quality
war nt Concerns
e Types Affected
Str
uct
ure
Mod Dec Modul Is a submodule Resource allocation Modifiability
ule omp e of and project structuring
stru ositi and planning;
ctur on encapsulation
es Use Modul Uses (i.e., Designing subsets and “Subsetabilit
s e requires the extensions y,”
correct extensibility
presence of)
Lay Layer Allowed to use Incremental Portability,
ers the services of; development; modifiability
provides implementing systems
abstraction to on top of “virtual
machines”
Clas Class, Is an instance In object-oriented Modifiability
s object of; is a systems, factoring out , extensibility
generalization commonality; planning
of extensions of
functionality
Dat Data {one, many}- Engineering global Modifiability
a entity to-{one, data structures for ,
mod many}; consistency and performance
el generalizes; performance
specializes
C& Serv Service Attachment Scheduling analysis; Interoperabili
C ice , (via message- performance analysis; ty,
stru service passing) robustness analysis availability,
ctur registr modifiability
es y
Soft Eleme Relations Useful for Quality
war nt Concerns
e Types Affected
Str
uct
ure
Con Proces Attachment Identifying locations Performance
curr ses, (via where resource
enc threads communication contention exists,
y and opportunities for
synchronization parallelism
mechanisms)
Allo Dep Compo Allocated to; Mapping software Performance,
cati loy nents, migrates to elements to system security,
on men hardwa elements energy,
stru t re availability,
ctur elemen deployability
es ts
Imp Modul Stored in Configuration control, Development
lem es, file integration, test efficiency
enta structu activities
tion re
Wor Modul Assigned to Project management, Development
k es, best use of expertise efficiency
assi organiz and available
gnm ational resources,
ent units management of
commonality
Relating Structures to Each Other
Each of these structures provides a different perspective and design
handle on a system, and each is valid and useful in its own right.
Although the structures give different system perspectives, they are not
independent. Elements of one structure will be related to elements of
other structures, and we need to reason about these relations. For
example, a module in a decomposition structure may be manifested as
one, part of one, or several components in one of the C&C structures,
reflecting its runtime alter-ego. In general, mappings between structures
are many to many.
Figure 1.11 shows a simple example of how two structures might
relate to each other. The image on the left shows a module
decomposition view of a tiny client-server system. In this system, two
modules must be implemented: the client software and the server
software. The image on the right shows a C&C view of the same system.
At runtime, ten clients are running and accessing the server. Thus this
little system has two modules and eleven components (and ten
connectors).
Figure 1.11 Two views of a client-server system
Whereas the correspondence between the elements in the
decomposition structure and the client-server structure is obvious, these
two views are used for very different things. For example, the view on
the right could be used for performance analysis, bottleneck prediction,
and network traffic management, which would be extremely difficult or
impossible to do with the view on the left. (In Chapter 9, we’ll learn
about the map-reduce pattern, in which copies of simple, identical
functionality are distributed across hundreds or thousands of processing
nodes—one module for the whole system, but one component per node.)
Individual projects sometimes consider one structure to be dominant
and cast other structures, when possible, in terms of the dominant
structure. Often, the dominant structure is the module decomposition
structure, and for good reason: It tends to spawn the project structure,
since it mirrors the team structure of development. In other projects, the
dominant structure might be a C&C structure that shows how the
system’s functionality and/or critical quality attributes are achieved at
runtime.
Fewer Is Better
Not all systems warrant consideration of many architectural structures.
The larger the system, the more dramatic the difference between these
structures tends to be; but for small systems, we can often get by with
fewer structures. For example, instead of working with each of several
C&C structures, usually a single one will do. If there is only one process,
then the process structure collapses to a single node and need not be
explicitly represented in the design. If no distribution will occur (that is,
if the system is implemented on a single processor), then the deployment
structure is trivial and need not be considered further. In general, you
should design and document a structure only if doing so brings a positive
return on the investment, usually in terms of decreased development or
maintenance costs.
Which Structures to Choose?
We have briefly described a number of useful architectural structures,
and many more are certainly possible. Which ones should an architect
choose to work on? Which ones should the architect choose to
document? Surely not all of them. A good answer is that you should
think about how the various structures available to you provide insight
and leverage into the system’s most important quality attributes, and then
choose the ones that will play the best role in delivering those attributes.
Architectural Patterns
In some cases, architectural elements are composed in ways that solve
particular problems. These compositions have been found to be useful
over time and over many different domains, so they have been
documented and disseminated. These compositions of architectural
elements, which provide packaged strategies for solving some of the
problems facing a system, are called patterns. Architectural patterns are
discussed in detail in Part II of this book.
1.3 What Makes a “Good” Architecture?
There is no such thing as an inherently good or bad architecture.
Architectures are either more or less fit for some purpose. A three-tier
layered service-oriented architecture may be just the ticket for a large
enterprise’s web-based B2B system but completely wrong for an
avionics application. An architecture carefully crafted to achieve high
modifiability does not make sense for a throw-away prototype (and vice
versa!). One of the messages of this book is that architectures can, in
fact, be evaluated—one of the great benefits of paying attention to them
—but such evaluation only makes sense in the context of specific stated
goals.
Nevertheless, some rules of thumb should be followed when
designing most architectures. Failure to apply any of these guidelines
does not automatically mean that the architecture will be fatally flawed,
but it should at least serve as a warning sign that should be investigated.
These rules can be applied proactively for greenfield development, to
help build the system “right.” Or they can be applied as analysis
heuristics, to understand the potential problem areas in existing systems
and to guide the direction of its evolution.
We divide our observations into two clusters: process
recommendations and product (or structural) recommendations. Our
process recommendations are as follows:
1. A software (or system) architecture should be the product of a
single architect or a small group of architects with an identified
technical leader. This approach is important to give the architecture
its conceptual integrity and technical consistency. This
recommendation holds for agile and open source projects as well
as “traditional” ones. There should be a strong connection between
the architects and the development team, to avoid “ivory tower,”
impractical designs.
2. The architect (or architecture team) should, on an ongoing basis,
base the architecture on a prioritized list of well-specified quality
attribute requirements. These will inform the tradeoffs that always
occur. Functionality matters less.
3. The architecture should be documented using views. (A view is
simply a representation of one or more architectural structures.)
The views should address the concerns of the most important
stakeholders in support of the project timeline. This might mean
minimal documentation at first, with the documentation then being
elaborated later. Concerns usually are related to construction,
analysis, and maintenance of the system, as well as education of
new stakeholders.
4. The architecture should be evaluated for its ability to deliver the
system’s important quality attributes. This should occur early in
the life cycle, when it returns the most benefit, and repeated as
appropriate, to ensure that changes to the architecture (or the
environment for which it is intended) have not rendered the design
obsolete.
5. The architecture should lend itself to incremental implementation,
to avoid having to integrate everything at once (which almost
never works) as well as to discover problems early. One way to do
this is via the creation of a “skeletal” system in which the
communication paths are exercised but which at first has minimal
functionality. This skeletal system can be used to “grow” the
system incrementally, refactoring as necessary.
Our structural rules of thumb are as follows:
1. The architecture should feature well-defined modules whose
functional responsibilities are assigned on the principles of
information hiding and separation of concerns. The information-
hiding modules should encapsulate things likely to change, thereby
insulating the software from the effects of those changes. Each
module should have a well-defined interface that encapsulates or
“hides” the changeable aspects from other software that uses its
facilities. These interfaces should allow their respective
development teams to work largely independently of each other.
2. Unless your requirements are unprecedented—possible, but
unlikely—your quality attributes should be achieved by using
well-known architectural patterns and tactics (described in
Chapters 4 through 13) specific to each attribute.
3. The architecture should never depend on a particular version of a
commercial product or tool. If it must, it should be structured so
that changing to a different version is straightforward and
inexpensive.
4. Modules that produce data should be separate from modules that
consume data. This tends to increase modifiability because
changes are frequently confined to either the production or the
consumption side of data. If new data is added, both sides will
have to change, but the separation allows for a staged
(incremental) upgrade.
5. Don’t expect a one-to-one correspondence between modules and
components. For example, in systems with concurrency, multiple
instances of a component may be running in parallel, where each
component is built from the same module. For systems with
multiple threads of concurrency, each thread may use services from
several components, each of which was built from a different
module.
6. Every process should be written so that its assignment to a specific
processor can be easily changed, perhaps even at runtime. This is a
driving force in the increasing trends toward virtualization and
cloud deployment, as we will discuss in Chapters 16 and 17.
7. The architecture should feature a small number of simple
component interaction patterns. That is, the system should do the
same things in the same way throughout. This practice will aid in
understandability, reduce development time, increase reliability,
and enhance modifiability.
8. The architecture should contain a specific (and small) set of
resource contention areas, whose resolution is clearly specified and
maintained. For example, if network utilization is an area of
concern, the architect should produce (and enforce) for each
development team guidelines that will result in acceptable levels of
network traffic. If performance is a concern, the architect should
produce (and enforce) time budgets.
1.4 Summary
The software architecture of a system is the set of structures needed to
reason about the system. These structures comprise software elements,
relations among them, and properties of both.
There are three categories of structures:
Module structures show the system as a set of code or data units that
have to be constructed or procured.
Component-and-connector structures show the system as a set of
elements that have runtime behavior (components) and interactions
(connectors).
Allocation structures show how elements from module and C&C
structures relate to nonsoftware structures (such as CPUs, file
systems, networks, and development teams).
Structures represent the primary engineering leverage points of an
architecture. Each structure brings with it the power to manipulate one or
more quality attributes. Collectively, structures represent a powerful
approach for creating the architecture (and, later, for analyzing it and
explaining it to its stakeholders). And, as we will see in Chapter 22, the
structures that the architect has chosen as engineering leverage points are
also the primary candidates to choose as the basis for architecture
documentation.
Every system has a software architecture, but this architecture may or
may not be documented and disseminated.
There is no such thing as an inherently good or bad architecture.
Architectures are either more or less fit for some purpose.
1.5 For Further Reading
If you’re keenly interested in software architecture as a field of study,
you might be interested in reading some of the pioneering work. Most of
it does not mention “software architecture” at all, as this phrase evolved
only in the mid-1990s, so you’ll have to read between the lines.
Edsger Dijkstra’s 1968 paper on the T.H.E. operating system
introduced the concept of layers [Dijkstra 68]. The early work of David
Parnas laid many conceptual foundations, including information hiding
[Parnas 72], program families [Parnas 76], the structures inherent in
software systems [Parnas 74], and the uses structure to build subsets and
supersets of systems [Parnas 79]. All of Parnas’s papers can be found in
the more easily accessible collection of his important papers [Hoffman
00]. Modern distributed systems owe their existence to the concept of
cooperating sequential processes that (among others) Sir C. A. R. (Tony)
Hoare was instrumental in conceptualizing and defining [Hoare 85].
3
Understanding Quality Attributes
Quality is never an accident; it is always the result of high intention,
sincere effort, intelligent direction and skillful execution.
—William A. Foster
Many factors determine the qualities that must be provided for in a
system’s architecture. These qualities go beyond functionality, which is
the basic statement of the system’s capabilities, services, and behavior.
Although functionality and other qualities are closely related, as you will
see, functionality often takes the front seat in the development scheme.
This preference is shortsighted, however. Systems are frequently
redesigned not because they are functionally deficient—the replacements
are often functionally identical—but because they are difficult to
maintain, port, or scale; or they are too slow; or they have been
compromised by hackers. In Chapter 2, we said that architecture was the
first place in software creation in which the achievement of quality
requirements could be addressed. It is the mapping of a system’s
functionality onto software structures that determines the architecture’s
support for qualities. In Chapters 4–14, we discuss how various qualities
are supported by architectural design decisions. In Chapter 20, we show
how to integrate all of your drivers, including quality attribute decisions,
into a coherent design.
We have been using the term “quality attribute” loosely, but now it is
time to define it more carefully. A quality attribute (QA) is a measurable
or testable property of a system that is used to indicate how well the
system satisfies the needs of its stakeholders beyond the basic function
of the system. You can think of a quality attribute as measuring the
“utility” of a product along some dimension of interest to a stakeholder.
In this chapter our focus is on understanding the following:
How to express the qualities we want our architecture to exhibit
How to achieve the qualities through architectural means
How to determine the design decisions we might make with respect
to the qualities
This chapter provides the context for the discussions of individual
quality attributes in Chapters 4–14.
3.1 Functionality
Functionality is the ability of the system to do the work for which it was
intended. Of all of the requirements, functionality has the strangest
relationship to architecture.
First of all, functionality does not determine architecture. That is,
given a set of required functionality, there is no end to the architectures
you could create to satisfy that functionality. At the very least, you could
divide up the functionality in any number of ways and assign the sub-
pieces to different architectural elements.
In fact, if functionality were the only thing that mattered, you
wouldn’t have to divide the system into pieces at all: A single monolithic
blob with no internal structure would do just fine. Instead, we design our
systems as structured sets of cooperating architectural elements—
modules, layers, classes, services, databases, apps, threads, peers, tiers,
and on and on—to make them understandable and to support a variety of
other purposes. Those “other purposes” are the other quality attributes
that we’ll examine in the remaining sections of this chapter, and in the
subsequent quality attribute chapters in Part II.
Although functionality is independent of any particular structure, it is
achieved by assigning responsibilities to architectural elements. This
process results in one of the most basic architectural structures—module
decomposition.
Although responsibilities can be allocated arbitrarily to any module,
software architecture constrains this allocation when other quality
attributes are important. For example, systems are frequently (or perhaps
always) divided so that several people can cooperatively build them. The
architect’s interest in functionality is how it interacts with and constrains
other qualities.
Functional Requirements
After more than 30 years of writing about and discussing the
distinction between functional requirements and quality
requirements, the definition of functional requirements still eludes
me. Quality attribute requirements are well defined: Performance
has to do with the system’s timing behavior, modifiability has to do
with the system’s ability to support changes in its behavior or other
qualities after initial deployment, availability has to do with the
system’s ability to survive failures, and so forth.
Function, however, is a much more slippery concept. An
international standard (ISO 25010) defines functional suitability as
“the capability of the software product to provide functions which
meet stated and implied needs when the software is used under
specified conditions.” That is, functionality is the ability to provide
functions. One interpretation of this definition is that functionality
describes what the system does and quality describes how well the
system does its function. That is, qualities are attributes of the
system and function is the purpose of the system.
This distinction breaks down, however, when you consider the
nature of some of the ”function.” If the function of the software is
to control engine behavior, how can the function be correctly
implemented without considering timing behavior? Is the ability to
control access by requiring a user name/password combination not
a function, even though it is not the purpose of any system?
I much prefer using the word “responsibility” to describe
computations that a system must perform. Questions such as “What
are the timing constraints on that set of responsibilities?”, “What
modifications are anticipated with respect to that set of
responsibilities?”, and “What class of users is allowed to execute
that set of responsibilities?” make sense and are actionable.
The achievement of qualities induces responsibility; think of the
user name/password example just mentioned. Further, one can
identify responsibilities as being associated with a particular set of
requirements.
So does this mean that the term “functional requirement”
shouldn’t be used? People have an understanding of the term, but
when precision is desired, we should talk about sets of specific
responsibilities instead.
Paul Clements has long ranted against the careless use of the
term “nonfunctional,” and now it’s my turn to rant against the
careless use of the term “functional”—which is probably equally
ineffectually.
—LB
3.2 Quality Attribute Considerations
Just as a system’s functions do not stand on their own without due
consideration of quality attributes, neither do quality attributes stand on
their own; they pertain to the functions of the system. If a functional
requirement is “When the user presses the green button, the Options
dialog appears,” a performance QA annotation might describe how
quickly the dialog will appear; an availability QA annotation might
describe how often this function is allowed to fail, and how quickly it
will be repaired; a usability QA annotation might describe how easy it is
to learn this function.
Quality attributes as a distinct topic have been studied by the software
community at least since the 1970s. A variety of taxonomies and
definitions have been published (we discuss some of these in Chapter
14), many of which have their own research and practitioner
communities. However, there are three problems with most discussions
of system quality attributes:
1. The definitions provided for an attribute are not testable. It is
meaningless to say that a system will be “modifiable.” Every
system will be modifiable with respect to one set of changes and
not modifiable with respect to another. The other quality attributes
are similar in this regard: A system may be robust with respect to
some faults and brittle with respect to others, and so forth.
2. Discussion often focuses on which quality a particular issue
belongs to. Is a denial-of-service attack on a system an aspect of
availability, an aspect of performance, an aspect of security, or an
aspect of usability? All four attribute communities would claim
“ownership” of the denial-of-service attack. All are, to some
extent, correct. But this debate over categorization doesn’t help us,
as architects, understand and create architectural solutions to
actually manage the attributes of concern.
3. Each attribute community has developed its own vocabulary. The
performance community has “events” arriving at a system, the
security community has “attacks” arriving at a system, the
availability community has “faults” arriving, and the usability
community has “user input.” All of these may actually refer to the
same occurrence, but they are described using different terms.
A solution to the first two problems (untestable definitions and
overlapping issues) is to use quality attribute scenarios as a means of
characterizing quality attributes (see Section 3.3). A solution to the third
problem is to illustrate the concepts that are fundamental to that attribute
community in a common form, which we do in Chapters 4–14.
We will focus on two categories of quality attributes. The first
category includes those attributes that describe some property of the
system at runtime, such as availability, performance, or usability. The
second category includes those that describe some property of the
development of the system, such as modifiability, testability, or
deployability.
Quality attributes can never be achieved in isolation. The achievement
of any one will have an effect—sometimes positive and sometimes
negative—on the achievement of others. For example, almost every
quality attribute negatively affects performance. Take portability: The
main technique for achieving portable software is to isolate system
dependencies, which introduces overhead into the system’s execution,
typically as process or procedure boundaries, which then hurts
performance. Determining a design that may satisfy quality attribute
requirements is partially a matter of making the appropriate tradeoffs;
we discuss design in Chapter 21.
In the next three sections, we focus on how quality attributes can be
specified, what architectural decisions will enable the achievement of
particular quality attributes, and what questions about quality attributes
will enable the architect to make the correct design decisions.
3.3 Specifying Quality Attribute Requirements:
Quality Attribute Scenarios
We use a common form to specify all QA requirements as scenarios.
This addresses the vocabulary problems we identified previously. The
common form is testable and unambiguous; it is not sensitive to whims
of categorization. Thus it provides regularity in how we treat all quality
attributes.
Quality attribute scenarios have six parts:
Stimulus. We use the term “stimulus” to describe an event arriving at
the system or the project. The stimulus can be an event to the
performance community, a user operation to the usability
community, or an attack to the security community, and so forth. We
use the same term to describe a motivating action for developmental
qualities. Thus a stimulus for modifiability is a request for a
modification; a stimulus for testability is the completion of a unit of
development.
Stimulus source. A stimulus must have a source—it must come from
somewhere. Some entity (a human, a computer system, or any other
actor) must have generated the stimulus. The source of the stimulus
may affect how it is treated by the system. A request from a trusted
user will not undergo the same scrutiny as a request by an untrusted
user.
Response. The response is the activity that occurs as the result of the
arrival of the stimulus. The response is something the architect
undertakes to satisfy. It consists of the responsibilities that the
system (for runtime qualities) or the developers (for development-
time qualities) should perform in response to the stimulus. For
example, in a performance scenario, an event arrives (the stimulus)
and the system should process that event and generate a response. In
a modifiability scenario, a request for a modification arrives (the
stimulus) and the developers should implement the modification—
without side effects—and then test and deploy the modification.
Response measure. When the response occurs, it should be
measurable in some fashion so that the scenario can be tested—that
is, so that we can determine if the architect achieved it. For
performance, this could be a measure of latency or throughput; for
modifiability, it could be the labor or wall clock time required to
make, test, and deploy the modification.
These four characteristics of a scenario are the heart of our quality
attribute specifications. But two more characteristics are important, yet
often overlooked: environment and artifact.
Environment. The environment is the set of circumstances in which
the scenario takes place. Often this refers to a runtime state: The
system may be in an overload condition or in normal operation, or
some other relevant state. For many systems, “normal” operation can
refer to one of a number of modes. For these kinds of systems, the
environment should specify in which mode the system is executing.
But the environment can also refer to states in which the system is
not running at all: when it is in development, or testing, or refreshing
its data, or recharging its battery between runs. The environment sets
the context for the rest of the scenario. For example, a request for a
modification that arrives after the code has been frozen for a release
may be treated differently than one that arrives before the freeze.
The fifth successive failure of a component may be treated
differently than the first failure of that component.
Artifact. The stimulus arrives at some target. This is often captured
as just the system or project itself, but it’s helpful to be more precise
if possible. The artifact may be a collection of systems, the whole
system, or one or more pieces of the system. A failure or a change
request may affect just a small portion of the system. A failure in a
data store may be treated differently than a failure in the metadata
store. Modifications to the user interface may have faster response
times than modifications to the middleware.
To summarize, we capture quality attribute requirements as six-part
scenarios. While it is common to omit one or more of these six parts,
particularly in the early stages of thinking about quality attributes,
knowing that all of the parts are there forces the architect to consider
whether each part is relevant.
We have created a general scenario for each of the quality attributes
presented in Chapters 4–13 to facilitate brainstorming and elicitation of
concrete scenarios. We distinguish general quality attribute scenarios—
general scenarios—which are system independent and can pertain to any
system, from concrete quality attribute scenarios—concrete scenarios—
which are specific to the particular system under consideration.
To translate these generic attribute characterizations into requirements
for a particular system, the general scenarios need to be made system
specific. But, as we have found, it is much easier for a stakeholder to
tailor a general scenario into one that fits their system than it is for them
to generate a scenario from thin air.
Figure 3.1 shows the parts of a quality attribute scenario just
discussed. Figure 3.2 shows an example of a general scenario, in this
instance for availability.
Figure 3.1 The parts of a quality attribute scenario
Figure 3.2 A general scenario for availability
Not My Problem
Some time ago I was doing an architecture analysis on a complex
system created by and for Lawrence Livermore National
Laboratory. If you visit this organization’s website (llnl.gov) and try
to figure out what Livermore Labs does, you will see the word
“security” mentioned over and over. The lab focuses on nuclear
security, international and domestic security, and environmental and
energy security. Serious stuff . . .
Keeping this emphasis in mind, I asked my clients to describe
the quality attributes of concern for the system that I was
analyzing. I’m sure you can imagine my surprise when security
wasn’t mentioned once! The system stakeholders mentioned
performance, modifiability, evolvability, interoperability,
configurability, and portability, and one or two more, but the word
“security” never passed their lips.
Being a good analyst, I questioned this seemingly shocking and
obvious omission. Their answer was simple and, in retrospect,
straightforward: “We don’t care about it. Our systems are not
connected to any external network, and we have barbed-wire
fences and guards with machine guns.”
Of course, someone at Livermore Labs was very interested in
security. But not the software architects. The lesson here is that the
software architect may not bear the responsibility for every QA
requirement.
—RK
3.4 Achieving Quality Attributes through
Architectural Patterns and Tactics
We now turn to the techniques an architect can use to achieve the
required quality attributes: architectural patterns and tactics.
A tactic is a design decision that influences the achievement of a
quality attribute response—it directly affects the system’s response to
some stimulus. Tactics may impart portability to one design, high
performance to another, and integrability to a third.
An architectural pattern describes a particular recurring design
problem that arises in specific design contexts and presents a well-
proven architectural solution for the problem. The solution is specified
by describing the roles of its constituent elements, their responsibilities
and relationships, and the ways in which they collaborate. Like the
choice of tactics, the choice of an architectural pattern has a profound
effect on quality attributes—usually more than one.
Patterns typically comprise multiple design decisions and, in fact,
often comprise multiple quality attribute tactics. We say that patterns
often bundle tactics and, consequently, frequently make tradeoffs among
quality attributes.
We will look at example relationships between tactics and patterns in
each of our quality attribute–specific chapters. Chapter 14 explains how
a set of tactics for any quality attribute can be constructed; those tactics
are, in fact, the steps we used to produce the sets found in this book.
While we discuss patterns and tactics as though they were
foundational design decisions, the reality is that architectures often
emerge and evolve as a result of many small decisions and business
forces. For example, a system that was once tolerably modifiable may
deteriorate over time, through the actions of developers adding features
and fixing bugs. Similarly, a system’s performance, availability, security,
and any other quality may (and typically does) deteriorate over time,
again through the well-intentioned actions of programmers who are
focused on their immediate tasks and not on preserving architectural
integrity.
This “death by a thousand cuts” is common on software projects.
Developers may make suboptimal decisions due to a lack of
understanding of the structures of the system, schedule pressures, or
perhaps a lack of clarity in the architecture from the start. This kind of
deterioration is a form of technical debt known as architecture debt. We
discuss architecture debt in Chapter 23. To reverse this debt, we typically
refactor.
Refactoring may be done for many reasons. For example, you might
refactor a system to improve its security, placing different modules into
different subsystems based on their security properties. Or you might
refactor a system to improve its performance, removing bottlenecks and
rewriting slow portions of the code. Or you might refactor to improve
the system’s modifiability. For example, when two modules are affected
by the same kinds of changes over and over because they are (at least
partial) duplicates of each other, the common functionality could be
factored out into its own module, thereby improving cohesion and
reducing the number of places that need to be changed when the next
(similar) change request arrives.
Code refactoring is a mainstay practice of agile development projects,
as a cleanup step to make sure that teams have not produced duplicative
or overly complex code. However, the concept applies to architectural
elements as well.
Successfully achieving quality attributes often involves process-
related decisions, in addition to architecture-related decisions. For
example, a great security architecture is worthless if your employees are
susceptible to phishing attacks or do not choose strong passwords. We
are not dealing with the process aspects in this book, but be aware that
they are important.
3.5 Designing with Tactics
A system design consists of a collection of decisions. Some of these
decisions help control the quality attribute responses; others ensure
achievement of system functionality. We depict this relationship in
Figure 3.3. Tactics, like patterns, are design techniques that architects
have been using for years. In this book, we isolate, catalog, and describe
them. We are not inventing tactics here, but rather just capturing what
good architects do in practice.
Figure 3.3 Tactics are intended to control responses to stimuli.
Why do we focus on tactics? There are three reasons:
1. Patterns are foundational for many architectures, but sometimes
there may be no pattern that solves your problem completely. For
example, you might need the high-availability high-security broker
pattern, not the textbook broker pattern. Architects frequently need
to modify and adapt patterns to their particular context, and tactics
provide a systematic means for augmenting an existing pattern to
fill the gaps.
2. If no pattern exists to realize the architect’s design goal, tactics
allow the architect to construct a design fragment from “first
principles.” Tactics give the architect insight into the properties of
the resulting design fragment.
3. Tactics provide a way of making design and analysis more
systematic within some limitations. We’ll explore this idea in the
next section.
Like any design concept, the tactics that we present here can and
should be refined as they are applied to design a system. Consider
performance: Schedule resources is a common performance tactic. But
this tactic needs to be refined into a specific scheduling strategy, such as
shortest-job-first, round-robin, and so forth, for specific purposes. Use
an intermediary is a modifiability tactic. But there are multiple types of
intermediaries (layers, brokers, proxies, and tiers, to name just a few),
which are realized in different ways. Thus a designer will employ
refinements to make each tactic concrete.
In addition, the application of a tactic depends on the context. Again,
consider performance: Manage sampling rate is relevant in some real-
time systems but not in all real-time systems, and certainly not in
database systems or stock-trading systems where losing a single event is
highly problematic.
Note that there are some “super-tactics”—tactics that are so
fundamental and so pervasive that they deserve special mention. For
example, the modifiability tactics of encapsulation, restricting
dependencies, using an intermediary, and abstracting common services
are found in the realization of almost every pattern ever! But other
tactics, such as the scheduling tactic from performance, also appear in
many places. For example, a load balancer is an intermediary that does
scheduling. We see monitoring appearing in many quality attributes: We
monitor aspects of a system to achieve energy efficiency, performance,
availability, and safety. Thus we should not expect a tactic to live in only
one place, for just a single quality attribute. Tactics are design primitives
and, as such, are found over and over in different aspects of design. This
is actually an argument for why tactics are so powerful and deserving of
our attention—and yours. Get to know them; they’ll be your friends.
3.6 Analyzing Quality Attribute Design Decisions:
Tactics-Based Questionnaires
In this section, we introduce a tool the analyst can use to understand
potential quality attribute behavior at various stages through the
architecture’s design: tactics-based questionnaires.
Analyzing how well quality attributes have been achieved is a critical
part of the task of designing an architecture. And (no surprise) you
shouldn’t wait until your design is complete before you begin to do it.
Opportunities for quality attribute analysis crop up at many different
points in the software development life cycle, even very early ones.
At any point, the analyst (who might be the architect) needs to
respond appropriately to whatever artifacts have been made available for
analysis. The accuracy of the analysis and expected degree of confidence
in the analysis results will vary according to the maturity of the available
artifacts. But no matter the state of the design, we have found tactics-
based questionnaires to be helpful in gaining insights into the
architecture’s ability (or likely ability, as it is refined) to provide the
needed quality attributes.
In Chapters 4–13, we include a tactics-based questionnaire for each
quality attribute covered in the chapters. For each question in the
questionnaire, the analyst records the following information:
Whether each tactic is supported by the system’s architecture.
Whether there are any obvious risks in the use (or nonuse) of this
tactic. If the tactic has been used, record how it is realized in the
system, or how it is intended to be realized (e.g., via custom code,
generic frameworks, or externally produced components).
The specific design decisions made to realize the tactic and where in
the code base the implementation (realization) may be found. This is
useful for auditing and architecture reconstruction purposes.
Any rationale or assumptions made in the realization of this tactic.
To use these questionnaires, simply follow these four steps:
22
Documenting an Architecture
Documentation is a love letter that you write to your future self.
—Damian Conway
Creating an architecture isn’t enough. It has to be communicated in a
way to let its stakeholders use it properly to do their jobs. If you go to the
trouble of creating a strong architecture, one that you expect to stand the
test of time, then you must go to the trouble of describing it in enough
detail, without ambiguity, and organized so that others can quickly find
and update needed information.
Documentation speaks for the architect. It speaks for the architect
today, when the architect should be doing other things besides answering
a hundred questions about the architecture. And it speaks for the
architect tomorrow, who has forgotten the details of what the
architecture includes, or when that person has left the project and
someone else is now the architect.
The best architects produce good documentation not because it’s
“required,” but because they see that it is essential to the matter at hand
—producing a high-quality product, predictably and with as little rework
as possible. They see their immediate stakeholders as the people most
intimately involved in this undertaking: developers, deployers, testers,
analysts.
But architects also see documentation as delivering value to
themselves. Documentation serves as the receptacle to hold the results of
major design decisions as they are confirmed. A well-thought-out
documentation scheme can make the process of design go much more
smoothly and systematically. Documentation helps the architect(s)
reason about the architecture design and communicate it while the
architecting is in progress, whether in a six-month design phase or a six-
day Agile sprint.
Note that “documentation” doesn’t necessarily mean producing a
physical, printed, book-like artifact. Online documentation such as a
wiki, hosted in ways that can engender discussion, stakeholder feedback,
and searching, is an ideal forum for architecture documentation. Also,
don’t think of documentation as a step that is distinct from and follows
design. The language you use to explain the architecture to others can be
used by you as you carry out your design work. Design and
documentation are, ideally, the same piece of work.
22.1 Uses and Audiences for Architecture
Documentation
Architecture documentation must serve varied purposes. It should be
sufficiently transparent and accessible to be quickly understood by new
employees. It should be sufficiently concrete to serve as a blueprint for
construction or forensics. It should have enough information to serve as a
basis for analysis.
Architecture documentation can be seen as both prescriptive and
descriptive. For some audiences, it prescribes what should be true,
placing constraints on decisions yet to be made. For other audiences, it
describes what is true, recounting decisions already made about a
system’s design.
Many different kinds of people will have an interest in architecture
documentation. They hope and expect that this documentation will help
them do their respective jobs. Understanding the uses of architecture
documentation is essential, as those uses determine the important
information to capture.
Fundamentally, architecture documentation has four uses.
1. Architecture documentation serves as a means of education. The
educational use consists of introducing people to the system. The
people may be new members of the team, external analysts, or
even a new architect. In many cases, the “new” person is the
customer to whom you’re showing your solution for the first time
—a presentation you hope will result in funding or go-ahead
approval.
2. Architecture documentation serves as a primary vehicle for
communication among stakeholders. Its precise use as a
communication vehicle depends on which stakeholders are doing
the communicating.
Perhaps one of the most avid consumers of architecture
documentation is none other than the project’s future architect.
That may be the same person (as noted in the quotation that
opened this chapter) or it may be a replacement, but in either case
the future architect is guaranteed to have an enormous stake in the
documentation. New architects are interested in learning how their
predecessors tackled the difficult issues of the system and why
particular decisions were made. Even if the future architect is the
same person, he or she will use the documentation as a repository
of thought, a storehouse of design decisions too numerous and
hopelessly intertwined to ever be reproducible from memory alone.
We enumerate the stakeholders for architecture, and its
documentation, in Section 22.8.
3. Architecture documentation serves as the basis for system analysis
and construction. Architecture tells implementers which modules
to implement and how those modules are wired together. These
dependencies determine the other teams with which the
development team for the module must communicate.
For those interested in the design’s ability to meet the system’s
quality objectives, the architecture documentation serves as fodder
for evaluation. It must contain the information necessary to
evaluate a variety of attributes, such as security, performance,
usability, availability, and modifiability.
4. Architecture documentation serves as the basis for forensics when
an incident occurs. When an incident occurs, someone is
responsible for tracking down both the immediate cause of the
incident and the underlying cause. Information about the flow of
control immediately prior to the incident will provide the “as
executed” architecture. For example, a database of interface
specifications will provide context for the flow of control, and
component descriptions will indicate what should have happened
in each component on the trace of events.
For the documentation to continue to provide value over time, it needs
to be kept up to date.
22.2 Notations
Notations for documenting views differ considerably in their degree of
formality. Roughly speaking, there are three main categories of notation:
Informal notations. Views may be depicted (often graphically) using
general-purpose diagramming and editing tools and visual
conventions chosen for the system at hand. Most box-and-line
drawings you’ve probably seen fall into this category—think
PowerPoint or something similar, or hand-drawn sketches on a
whiteboard. The semantics of the description are characterized in
natural language, and cannot be formally analyzed.
Semiformal notations. Views may be expressed in a standardized
notation that prescribes graphical elements and rules of construction,
but does not provide a complete semantic treatment of the meaning
of those elements. Rudimentary analysis can be applied to determine
if a description satisfies syntactic properties. UML and its system-
engineering adjunct SysML are semiformal notations in this sense.
Most widely used commercially available modeling tools employ
notations in this category.
Formal notations. Views may be described in a notation that has a
precise (usually mathematically based) semantics. Formal analysis
of both syntax and semantics is possible. A variety of formal
notations for software architecture are available. Generally referred
to as architecture description languages (ADLs), they typically
provide both a graphical vocabulary and an underlying semantics for
architecture representation. In some cases, these notations are
specialized to particular architectural views. In other cases, they
allow many views, or even provide the ability to formally define
new views. The usefulness of ADLs lies in their ability to support
automation through associated tools—automation to provide useful
analysis of the architecture, or assist in code generation. In practice,
the use of formal notations is rare.
Typically, more formal notations take more time and effort to create
and understand, but repay this effort with reduced ambiguity and more
opportunities for analysis. Conversely, more informal notations are
easier to create, but provide fewer guarantees.
Regardless of the level of formality, always remember that different
notations are better (or worse) for expressing different kinds of
information. Formality aside, no UML class diagram will help you
reason about schedulability, nor will a sequence diagram tell you very
much about the system’s likelihood of being delivered on time. You
should choose your notations and representation languages while
keeping in mind the important issues you need to capture and reason
about.
22.3 Views
Perhaps the most important concept associated with software architecture
documentation is that of the view. A software architecture is a complex
entity that cannot be described in a simple one-dimensional fashion. A
view is a representation of a set of system elements and relations among
them—not all system elements, but those of a particular type. For
example, a layered view of a system would show elements of type
“layer”; that is, it would show the system’s decomposition into layers,
along with the relations among those layers. A pure layered view would
not, however, show the system’s services, or clients and servers, or data
model, or any other type of element.
Thus views let us divide the multidimensional entity that is a software
architecture into a number of (we hope) interesting and manageable
representations of the system. The concept of views leads to a basic
principle of architecture documentation:
Documenting an architecture is a matter of documenting the relevant
views and then adding documentation that applies to more than one
view.
What are the relevant views? This depends entirely on your goals. As
we saw previously, architecture documentation can serve many
purposes: a mission statement for implementers, a basis for analysis, the
specification for automatic code generation, the starting point for system
understanding and reverse engineering, or the blueprint for project
estimation and planning.
Different views also expose different quality attributes to different
degrees. In turn, the quality attributes that are of most concern to you
and the other stakeholders in the system’s development will affect which
views you choose to document. For instance, a module view will let you
reason about your system’s maintainability, a deployment view will let
you reason about your system’s performance and reliability, and so forth.
Because different views support different goals and uses, we do not
advocate using any particular view or collection of views. The views you
should document depend on the uses you expect to make of the
documentation. Different views will highlight different system elements
and relations. How many different views to represent is the result of a
cost/benefit decision. Each view has a cost and a benefit, and you should
ensure that the expected benefits of creating and maintaining a particular
view outweigh its costs.
The choice of views is driven by the need to document a particular
pattern in your design. Some patterns are composed of modules, others
consist of components and connectors, and still others have deployment
considerations. Module views, component-and-connector (C&C) views,
and allocation views are the appropriate mechanism for representing
these considerations, respectively. These categories of views correspond,
of course, to the three categories of architectural structures described in
Chapter 1. (Recall from Chapter 1 that a structure is a collection of
elements, relations, and properties, whereas a view is a representation of
one or more architectural structures.)
In this section, we explore these three categories of structure-based
views and then introduce a new category: quality views.
Module Views
A module is an implementation unit that provides a coherent set of
responsibilities. A module might take the form of a class, a collection of
classes, a layer, an aspect, or any decomposition of the implementation
unit. Example module views are decomposition, uses, and layers. Every
module view has a collection of properties assigned to it. These
properties express important information associated with each module
and the relationships among the modules, as well as constraints on the
module. Example properties include responsibilities, visibility
information (what other modules can use it), and revision history. The
relations that modules have to one another include is-part-of, depends-
on, and is-a.
The way in which a system’s software is decomposed into manageable
units remains one of the important forms of system structure. At a
minimum, it determines how a system’s source code is decomposed into
units, what kinds of assumptions each unit can make about services
provided by other units, and how those units are aggregated into larger
ensembles. It also includes shared data structures that impact, and are
impacted by, multiple units. Module structures often determine how
changes to one part of a system might affect other parts and hence the
ability of a system to support modifiability, portability, and reuse.
The documentation of any software architecture is unlikely to be
complete without at least one module view. Table 22.1 summarizes the
characteristics of module views.
Table 22.1 Summary of Module Views
Ele Modules, which are implementation units of software that provide
men a coherent set of responsibilities
ts
Rela
tions
Is-part-of, which defines a part/whole relationship between
the submodule (the part) and the aggregate module (the
whole)
Depends-on, which defines a dependency relationship
between two modules
Is-a, which defines a generalization/specialization
relationship between a more specific module (the child) and a
more general module (the parent)
Con Different module views may impose topological constraints, such
strai as limitations on the visibility between modules.
nts
Usag
e
Blueprint for construction of the code
Analysis of the impact of changes
Planning incremental development
Requirements traceability analysis
Communicating the functionality of a system and the
structure of its code base
Supporting the definition of work assignments,
implementation schedules, and budget information
Showing the data model
Properties of modules that help to guide implementation or are input
into analysis should be recorded as part of the supporting documentation
for a module view. The list of properties may vary but is likely to
include the following:
Name. A module’s name is, of course, the primary means to refer to
it. A module’s name often suggests something about its role in the
system. In addition, a module’s name may reflect its position in a
decomposition hierarchy; the name A.B.C, for example, refers to a
module C that is a submodule of a module B, which is itself a
submodule of A.
Responsibilities. The responsibility property for a module is a way to
identify its role in the overall system and establishes an identity for it
beyond the name. Whereas a module’s name may suggest its role, a
statement of responsibility establishes that role with much more
certainty. Responsibilities should be described in sufficient detail to
make clear to the reader what each module does. A module’s
responsibilities are often captured by tracing to a project’s
requirements specification, if there is one.
Implementation information. Modules are units of implementation. It
is therefore useful to record information related to their
implementation from the point of view of managing their
development and building the system that contains them. This might
include:
Mapping to source code units. This identifies the files that
constitute the implementation of a module. For example, a
module Account, if implemented in Java, might have several
files that constitute its implementation: IAccount.java (an
interface), AccountImpl.java (implementation of Account
functionality), and perhaps even a unit test AccountTest.java.
Test information. The module’s test plan, test cases, test harness,
and test data are important to document. This information may
simply be a pointer to the location of these artifacts.
Management information. A manager may need information
about the module’s predicted schedule and budget. This
information may simply be a pointer to the location of these
artifacts.
Implementation constraints. In many cases, the architect will
have an implementation strategy in mind for a module or may
know of constraints that the implementation must follow.
Revision history. Knowing the history of a module, including its
authors and particular changes, may help you when you’re
performing maintenance activities.
A module view can be used to explain the system’s functionality to
someone not familiar with it. The various levels of granularity of the
module decomposition provide a top-down presentation of the system’s
responsibilities and, therefore, can guide the learning process. For a
system whose implementation is already in place, module views, if kept
up-to-date, are helpful because they explain the structure of the code
base to a new developer on the team.
Conversely, it is difficult to use the module views to make inferences
about runtime behavior, because these views are just a static partition of
the functions of the software. Thus a module view is not typically used
for analysis of performance, reliability, and many other runtime
qualities. For those purposes, we rely on component-and-connector and
allocation views.
Component-and-Connector Views
C&C views show elements that have some runtime presence, such as
processes, services, objects, clients, servers, and data stores. These
elements are termed components. Additionally, C&C views include as
elements the pathways of interaction, such as communication links and
protocols, information flows, and access to shared storage. Such
interactions are represented as connectors in C&C views. Example C&C
views include client-server, microservice, and communicating processes.
A component in a C&C view may represent a complex subsystem,
which itself can be described as a C&C subarchitecture. A component’s
subarchitecture may employ a different pattern than the one in which the
component appears.
Simple examples of connectors include service invocation,
asynchronous message queues, event multicast supporting publish-
subscribe interactions, and pipes that represent asynchronous, order-
preserving data streams. Connectors often represent much more complex
forms of interaction, such as a transaction-oriented communication
channel between a database server and a client, or an enterprise service
bus that mediates interactions between collections of service users and
providers.
Connectors need not be binary; that is, they need not have exactly two
components with which they interact. For example, a publish-subscribe
connector might have an arbitrary number of publishers and subscribers.
Even if the connector is ultimately implemented using binary
connectors, such as a procedure call, it can be useful to adopt n-ary
connector representations in a C&C view. Connectors embody a protocol
of interaction. When two or more components interact, they must obey
conventions about order of interactions, locus of control, and handling of
error conditions and timeouts. The protocol of interaction should be
documented.
The primary relation within a C&C view is attachment. Attachments
indicate which connectors are attached to which components, thereby
defining a system as a graph of components and connectors.
Compatibility often is defined in terms of information type and protocol.
For example, if a web server expects encrypted communication via
HTTPS, then the client must perform the encryption.
An element (component or connector) of a C&C view will have
various properties associated with it. Specifically, every element should
have a name and type, with its additional properties depending on the
type of component or connector. As an architect, you should define
values for the properties that support the intended analyses for the
particular C&C view. The following are examples of some typical
properties and their uses:
Reliability. What is the likelihood of failure for a given component
or connector? This property might be used to help determine overall
system availability.
Performance. What kinds of response time will the component
provide under what loads? What kind of bandwidth, latency, or jitter
can be expected for a given connector? This property can be used
with others to determine system-wide properties such as response
times, throughput, and buffering needs.
Resource requirements. What are the processing and storage needs
of a component or a connector? If relevant, how much energy does it
consume? This property can be used to determine whether a
proposed hardware configuration will be adequate.
Functionality. What functions does an element perform? This
property can be used to reason about the end-to-end computation
performed by a system.
Security. Does a component or a connector enforce or provide
security features, such as encryption, audit trails, or authentication?
This property can be used to determine potential system security
vulnerabilities.
Concurrency. Does this component execute as a separate process or
thread? This property can help to analyze or simulate the
performance of concurrent components and identify possible
deadlocks and bottlenecks.
Runtime extensibility. Does the messaging structure support evolving
data exchanges? Can the connectors be adapted to process those new
message types?
C&C views are commonly used to show developers and other
stakeholders how the system works: One can “animate” or trace through
a C&C view, showing an end-to-end thread of activity. C&C views are
also used to reason about runtime system quality attributes, such as
performance and availability. In particular, a well-documented view
allows architects to predict overall system properties such as latency or
reliability, given estimates or measurements of properties of the
individual elements and their interactions.
Table 22.2 summarizes the characteristics of C&C views.
Table 22.2 Summary of C&C Views
Eleme
nts
Components: principal processing units and data stores.
Connectors: pathways of interaction between components.
Relati
ons
Attachments: Components are associated with connectors to
yield a graph.
Constr Components can only be attached to connectors, and
aints connectors can only be attached to components.
Attachments can only be made between compatible
components and connectors.
Connectors cannot appear in isolation; a connector must be
attached to a component.
Usage Show how the system works.
Guide development by specifying the structure and
behavior of runtime elements.
Help reason about runtime system quality attributes, such as
performance and availability.
Notations for C&C Views
As always, box-and-line drawings are available to represent C&C views.
Although informal notations are limited in terms of the semantics that
they can convey, following some simple guidelines can lend rigor and
depth to the descriptions. The primary guideline is simple: Assign each
component type and each connector type a separate symbol, and list each
of the types in a key.
UML components are a good semantic match to C&C components
because they permit intuitive documentation of important information
such as interfaces, properties, and behavioral descriptions. UML
components also distinguish between component types and component
instances, which is useful when defining view-specific component types.
Allocation Views
Allocation views describe the mapping of software units to elements of
an environment in which the software is developed or in which it
executes. The environment in such a view varies; it might be the
hardware, the operating environment in which the software is executed,
the file systems supporting development or deployment, or the
development organization(s).
Table 22.3 summarizes the characteristics of allocation views. These
views consist of software elements and environmental elements.
Examples of environmental elements are a processor, a disk farm, a file
or folder, or a group of developers. The software elements come from a
module or C&C view.
Table 22.3 Summary of Allocation Views
El Software element and environmental element. A software element
e has properties that are required of the environment. An
m environmental element has properties that are provided to the
en software.
ts
R Allocated-to: A software element is mapped (allocated to) an
el environmental element.
ati
on
s
C Varies by view.
on
st
ra
in
ts
Us For reasoning about performance, availability, security, and safety.
ag For reasoning about distributed development and allocation of work
e to teams. For reasoning about concurrent access to software
versions. For reasoning about the form and mechanisms of system
installation.
The relation in an allocation view is allocated-to. We usually talk
about allocation views in terms of a mapping from software elements to
environmental elements, although the reverse mapping would also be
relevant and potentially interesting. A single software element can be
allocated to multiple environmental elements, and multiple software
elements can be allocated to a single environmental element. If these
allocations change over time, during execution of the system, then the
architecture is said to be dynamic with respect to that allocation. For
example, processes might migrate from one processor or virtual machine
to another.
Software elements and environmental elements have properties in
allocation views. One goal of an allocation view is to compare the
properties required by the software element with the properties provided
by the environmental elements to determine whether the allocation will
be successful. For example, to ensure its required response time, a
component has to execute on (be allocated to) a processor that provides
sufficiently fast processing power. As another example, a computing
platform might not allow a task to use more than 10 kilobytes of virtual
memory; an execution model of the software element in question can be
used to determine the required virtual memory usage. Similarly, if you
are migrating a module from one team to another, you might want to
ensure that the new team has the appropriate skills and background
knowledge to work with that module.
Allocation views can depict either static or dynamic views. A static
view illustrates a fixed allocation of resources in an environment. A
dynamic view shows the conditions and the triggers for which allocation
of resources changes. For example, some systems provision and utilize
new resources as their loads increase. An example is a load-balancing
system in which new processes or threads are created on another
machine. In this view, the conditions under which the allocation view
changes, the allocation of runtime software, and the dynamic allocation
mechanism need to be documented.
Recall from Chapter 1 that one of the allocation structures is the work
assignment structure, which allocates modules to teams for development.
That allocation can also be changed, depending on the “load”—in this
case, the load on development teams already at work.
Quality Views
Module, C&C, and allocation views are all structural views: They
primarily show the structures that the architect has designed into the
architecture to satisfy functional and quality attribute requirements.
These views are excellent choices for guiding and constraining
downstream developers, whose primary job is to implement those
structures. However, in systems in which certain quality attributes (or,
for that matter, any stakeholder concerns) are particularly important and
pervasive, structural views may not be the best way to present the
architectural solution to those needs. The reason is that the solution may
be spread across multiple structures that are cumbersome to combine
(e.g., because the element types shown in each structure are different).
Another kind of view, which we call a quality view, can be tailored for
specific stakeholders or to address specific concerns. Quality views are
formed by extracting the relevant pieces of structural views and
packaging them together. Here are five examples:
A security view can show all of the architectural measures taken to
provide security. It would depict the components that have some
security role or responsibility, how those components communicate,
any data repositories for security information, and repositories that
are of security interest. The view’s properties would include other
security measures (e.g., physical security) in the system’s
environment. The security view would also show the operation of
security protocols and where and how humans interact with the
security elements. Finally, it would capture how the system responds
to specific threats and vulnerabilities.
A communications view might be especially helpful for systems that
are globally dispersed and heterogeneous. This view would show all
of the component-to-component channels, various network channels,
quality-of-service parameter values, and areas of concurrency. Such
a view can be used to analyze certain kinds of performance and
reliability, such as deadlock or race condition detection. In addition,
it could show (for example) how network bandwidth is dynamically
allocated.
An exception or error-handling view could help illuminate and draw
attention to error reporting and resolution mechanisms. Such a view
would show how components detect, report, and resolve faults or
errors. It would help the architect identify the sources of errors and
specify appropriate corrective actions for each. Finally, it would
facilitate root-cause analysis in those cases.
A reliability view would model reliability mechanisms such as
replication and switch-over. It would also depict timing issues and
transaction integrity.
A performance view would include those aspects of the architecture
useful for inferring the system’s performance. Such a view might
show network traffic models, maximum latencies for operations, and
so forth.
These and other quality views reflect the documentation philosophy of
ISO/IEC/IEEE standard 42010:2011, which prescribes creating views
driven by the concerns of the architecture’s stakeholders.
22.4 Combining Views
The basic principle of documenting an architecture as a set of separate
views brings a divide-and-conquer advantage to the task of
documentation. Of course, if those views were irrevocably different, with
no association with one another, no one would be able to understand the
system as a whole. However, because all structures in an architecture are
part of the same architecture and exist to achieve a common purpose,
many of them have strong associations with each other. Managing how
architectural structures are associated is an important part of the
architect’s job, independently of whether any documentation of those
structures exists.
Sometimes the most convenient way to show a strong association
between two views is to collapse them into a single combined view. A
combined view contains elements and relations that come from two or
more other views. Such views can be very useful as long as you do not
try to overload them with too many mappings.
The easiest way to merge views is to create an overlay that combines
the information that would otherwise have appeared in two separate
views. This works well if the relationship between the two views is tight
—that is, if there are strong associations between elements in one view
and elements in the other view. In such a case, the structure described by
the combined view will be easier to understand than the two views seen
separately. In an overlay, the elements and the relations keep the types as
defined in their constituent views.
The following combinations of views often occur quite naturally:
C&C views with each other. Because all C&C views show runtime
relations among components and connectors of various types, they
tend to combine well. Different (separate) C&C views tend to show
different parts of the system, or tend to show decomposition
refinements of components in other views. The result is often a set
of views that can be combined easily.
Deployment view with any C&C view that shows processes.
Processes are the components that are deployed onto processors,
virtual machines, or containers. Thus there is a strong association
between the elements in these views.
Decomposition view and any work assignment, implementation,
uses, or layered views. The decomposed modules form the units of
work, development, and uses. In addition, these modules populate
layers.
Figure 22.1 shows an example of a combined view that is an overlay
of client-server, multi-tier, and deployment views.
Figure 22.1 A combined view
22.5 Documenting Behavior
Documenting an architecture requires behavior documentation that
complements the structural views by describing how architecture
elements interact with each other. Reasoning about characteristics such
as a system’s potential to deadlock, a system’s ability to complete a task
in the desired amount of time, or maximum memory consumption
requires that the architecture description provide information about the
characteristics of individual elements and their resource consumption, as
well as patterns of interaction among them—that is, how they behave in
relation to each other. In this section, we provide guidance as to what
types of things you will want to document to reap these benefits.
Two kinds of notations are available for documenting behavior: trace-
oriented and comprehensive.
Traces are sequences of activities or interactions that describe the
system’s response to a specific stimulus when the system is in a specific
state. A trace describes a sequence of activities or interactions between
structural elements of the system. Although one might conceivably
describe all possible traces to generate the equivalent of a
comprehensive behavioral model, trace-oriented documentation does not
really seek to do so. Here we describe four notations for documenting
traces: use cases, sequence diagrams, communication diagrams, and
activity diagrams. Although other notations are available (such as
message sequence charts, timing diagrams, and the Business Process
Execution Language), we have chosen these four as a representative
sample of trace-oriented notations.
Use cases describe how actors can use a system to accomplish their
goals; they are frequently used to capture the functional
requirements for a system. UML provides a graphical notation for
use case diagrams but does not specify how the text of a use case
should be written. The UML use case diagram is a good way to
provide an overview of the actors and the behavior of a system. Its
description, which is textual, should include the following items: the
use case name and a brief description, the actor or actors who initiate
the use case (primary actors), other actors who participate in the use
case (secondary actors), the flow of events, alternative flows, and
non-success cases.
A UML sequence diagram shows a sequence of interactions among
instances of elements pulled from the structural documentation. It is
useful, when designing a system, for identifying where interfaces
need to be defined. The sequence diagram shows only the instances
participating in the scenario being documented. It has two
dimensions: vertical, representing time, and horizontal, representing
the various instances. The interactions are arranged in time sequence
from top to bottom. Figure 22.2 is an example of a sequence diagram
that illustrates the basic UML notation. Sequence diagrams are not
explicit about showing concurrency. If that is your goal, use activity
diagrams instead.
Figure 22.2 A simple example of a UML sequence diagram
As shown in Figure 22.2, objects (i.e., element instances) have a
lifeline, drawn as a vertical dashed line down the time axis. The
sequence is usually started by an actor on the far left. The instances
interact by sending messages, which are shown as horizontal arrows.
A message can be a message sent over a network, a function call, or
an event sent through a queue. The message usually maps to a
resource (operation) in the interface of the receiver instance. A filled
arrowhead on a solid line represents a synchronous message,
whereas an open arrowhead represents an asynchronous message.
The dashed arrow is a return message. The execution occurrence
bars along the lifeline indicate that the instance is processing or
blocked waiting for a return.
A UML communication diagram shows a graph of interacting
elements and annotates each interaction with a number denoting its
order. Similar to sequence diagrams, instances shown in a
communication diagram are elements described in the accompanying
structural documentation. Communication diagrams are useful when
the task is to verify that an architecture can fulfill the functional
requirements. Such diagrams are not useful when understanding of
concurrent actions is important, as when conducting a performance
analysis.
UML activity diagrams are similar to flowcharts. They show a
business process as a sequence of steps (called actions) and include
notation to express conditional branching and concurrency, as well
as to show sending and receiving events. Arrows between actions
indicate the flow of control. Optionally, activity diagrams can
indicate the architecture element or actor performing the actions.
Notably, activity diagrams can express concurrency. A fork node
(depicted as a thick bar orthogonal to the flow arrows) splits the flow
into two or more concurrent flows of actions. These concurrent
flows may later be synchronized into a single flow through a join
node (also depicted as an orthogonal bar). The join node waits for all
incoming flows to complete before proceeding.
Unlike sequence and communication diagrams, activity diagrams don’t
show the actual operations being performed on specific objects. Thus
these diagrams are useful to broadly describe the steps in a specific
workflow. Conditional branching (shown by a diamond symbol) allows a
single diagram to represent multiple traces, although an activity diagram
usually does not attempt to show all possible traces or the complete
behavior for the system (or part of it). Figure 22.3 shows an activity
diagram.
Figure 22.3 Activity diagram
In contrast to trace notations, comprehensive notations show the
complete behavior of structural elements. Given this type of
documentation, it is possible to infer all possible paths from the initial
state to the final state. State machines are a kind of formalism used by
many comprehensive notations. This formalism represents the behavior
of architecture elements because each state is an abstraction of all
possible histories that could lead to that state. State machine languages
allow you to complement a structural description of the elements of the
system with constraints on interactions and timed reactions to both
internal and environmental stimuli.
UML state machine diagrams allow you to trace the behavior of your
system, given specific inputs. Such a diagram represents states using
boxes and transitions between states using arrows. Thus it models
elements of the architecture and helps illustrate their runtime
interactions. Figure 22.4 is an example of a state machine diagram
showing the states of a car stereo.
Figure 22.4 UML state machine diagram for a car stereo system
Each transition in a state machine diagram is labeled with the event
causing the transition. For example, in Figure 22.4, the transitions
correspond to the buttons the driver can press or driving actions that
affect the cruise control system. Optionally, the transition can specify a
guard condition, which is enclosed in brackets. When the event
corresponding to the transition occurs, the guard condition is evaluated
and the transition is enabled only if the guard is true at that time.
Transitions can also have consequences, called actions or effects, which
are indicated by a slash. When an action is present, it indicates that the
behavior following the slash will be performed when the transition
occurs. The states may also specify entry and exit actions.
22.6 Beyond Views
In addition to views and behavior, comprehensive information about an
architecture will include the following items:
Mapping between views. Because all the views of an architecture
describe the same system, it stands to reason that any two views will
have much in common. Combining views (as described in Section
22.4) produces a set of views. Illuminating the associations among
those views can then help that reader gain a powerful insight into
how the architecture works as a unified conceptual whole.
The associations between elements across views in an architecture
are, in general, many-to-many. For instance, each module may map
to multiple runtime elements, and each runtime element may map to
multiple modules.
View-to-view associations can be conveniently captured as tables.
To create such a table list the elements of the first view in some
convenient lookup order. The table itself should be annotated or
introduced with an explanation of the association that it depicts—
that is, the correspondence between the elements across the two
views. Examples include “is implemented by” for mapping from a
component-and-connector view to a module view, “implements” for
mapping from a module view to a component-and-connector view,
“included in” for mapping from a decomposition view to a layered
view, and many others.
Documenting patterns. If you employ patterns in your design, as
recommended in Chapter 20, these patterns should be identified in
the documentation. First, record the fact that the given pattern is
being used. Then say why this solution approach was chosen—why
the pattern is appropriate for the problem at hand. Using a pattern
involves making successive design decisions that eventually result in
that pattern’s instantiation. These design decisions may manifest
themselves as newly instantiated elements and the relations among
them, which in turn should be documented in structural views.
One or more context diagrams. A context diagram shows how the
system or portion of the system relates to its environment. The
purpose of this diagram is to depict the scope of a view. Here
“context” means an environment with which the (part of the) system
interacts. Entities in the environment may be humans, other
computer systems, or physical objects, such as sensors or controlled
devices. A context diagram may be created for each view, with each
diagram showing how different types of elements interact with the
system’s environment. Context diagrams are useful for presenting an
initial picture of how a system or subsystem interacts with its
environment.
Variability guide. A variability guide shows how to exercise any
variation points that are part of the architecture shown in this view.
Rationale. The rationale explains why the design reflected in the
view came to be. The goal of this section is to explain why the
design has its present form and to provide a convincing argument
that it is sound. Documenting the rationale is described in more
detail in Section 22.7.
Glossary and acronym list. Likely your architecture will contain
many specialized terms and acronyms. Decoding these for your
readers will ensure that all your stakeholders are speaking the same
language, as it were.
Document control information. List the issuing organization, the
current version number, the date of issue and status, a change
history, and the procedure for submitting change requests to the
document. Usually this information is captured in the front matter.
Change control tools can provide much of this information.
22.7 Documenting the Rationale
When designing, you make important design decisions to achieve the
goals of each iteration. These design decisions include:
Selecting a design concept from several alternatives
Creating structures by instantiating the selected design concept
Establishing relationships between elements and defining interfaces
Allocating resources (e.g., people, hardware, computation)
When you study a diagram that represents an architecture, you see the
end product of a thought process but can’t always easily understand the
decisions that were made to achieve this result. Recording design
decisions beyond the representation of the chosen elements,
relationships, and properties is fundamental to help in understanding
how you arrived at the result; in other words, it lays out the design
rationale.
When your iteration goal involves satisfying an important quality
attribute scenario, some of the decisions that you make will play a
significant role in achieving the scenario response measure.
Consequently, you should take the greatest care in recording these
decisions: They are essential to facilitate analysis of the design you
created, to facilitate implementation, and, still later, to aid in
understanding the architecture (e.g., during maintenance). Given that
most design decisions are “good enough,” and seldom optimal, you also
need to justify the decisions made, and to record the risks associated
with your decisions so that they may be reviewed and possibly revisited.
You may perceive recording design decisions as a tedious task.
However, depending on the criticality of the system being developed,
you can adjust the amount of information that is recorded. For example,
to record a minimum of information, you can use a simple table such as
Table 22.4. If you decide to record more than this minimum, the
following information might prove useful:
What evidence was produced to justify decisions?
Who did what?
Why were shortcuts taken?
Why were tradeoffs made?
What assumptions did you make?
Table 22.4 Example Table to Document Design Decisions
Design Decisions Rationale and Assumptions (Include
and Location Discarded Alternatives)
Introduce Concurrency should be introduced to be able to
concurrency (tactic) receive and process several events (traps)
in the simultaneously.
TimeServerConnecto
r and
FaultDetectionServic
e
Use of the messaging Although the use of a message queue imposes a
pattern through the performance penalty, a message queue was
introduction of a chosen because some implementations have high
message queue in the performance and, furthermore, this will be
communications helpful to support quality attribute scenario QA-
layer 3.
... ...
In the same way that we suggest that you record responsibilities as you
identify elements, you should record the design decisions as you make
them. If you leave it until later, you will not remember why you did
things.
22.8 Architecture Stakeholders
In Chapter 2, we said that one of the key purposes of architecture was to
enable communication among stakeholders. In this chapter, we have said
that architecture documentation is produced in service of architecture
stakeholders. So who are they?
The set of stakeholders will vary, depending on the organization and
the project. The list of stakeholders in this section is suggestive but is not
intended to be complete. As an architect, one of your primary
obligations is to identify the real stakeholders for your project. Similarly,
the documentation needs we lay out here for each stakeholder are typical
but not definitive. You’ll need to take the following discussion as a
starting point and adapt it according to the needs of your project.
Key stakeholders of an architecture include the following: