The design represents the behavior and structure of the system at various levels of abstraction – most notably not
solely at the code level of abstraction. This will help the designer reason about the quality, structure,
and behavior of the design.
Multiple Passes
The design will be revisited many times throughout the iterative lifecycle and even within an iteration.
Each time some design activity is being performed, it will be performed with some specific goal. The goal might
be to identify a notional set of participants in a collaboration that can be exercised to realize the behavior required
(an analysis pass). The goal might be in the identification of some coarse-grained elements that are required to
act out some scenario (an architectural pass). Then a pass might be done within one of those components to
identify the elements within that will collaborate together to perform the behavior required (a more detailed design
pass).
Design might be performed in a particular context such as database context, user-interface context, or perhaps the
context of how some existing library will be applied. In these cases the design steps will be performed just to
make and communicate decisions regarding that context.
Avoid analysis paralysis. Avoid refining, extending, and improving the design beyond a minimal version that
suffices to cover the needs of the requirements within the architecture. Design should be done in small chunks,
proven via implementation, improved via refactoring, and integrated into the baseline to provide value to the
stakeholders.
Identification of Elements
Identify the elements based on needs of the requirements.
The identification of elements can stem from a static perspective of walking through the requirements and identifying
elements clearly called out, a form of domain modeling. It can pull in other elements identified as being in the
application domain or deemed necessary from examining the requirements for the portion of the system being
designed. This can also pull from key abstractions identified while defining the architecture.
The identification of elements should apply a dynamic perspective by walking through scenarios
of usage of the system (or subsystem) identifying elements needed to support the behavior. That behavior
might be a scenario of usage from an external user perspective or, while designing a subsystem, a responsibility
assigned to the subsystem that has complex algorithmic behavior. Consider applying the Entity-Control-Boundary Pattern to identify the elements necessary to support
the scenario or apply other patterns identified in the architecture that specify the elements that will be used to
support specific aspects of the scenario.
If the system being designed is a real-time system, include elements representing events and signals that allow us to
describe the asynchronous triggers of behavior to which the system must respond. Events are specifications of
interesting occurrences in time and space that usually (if they are noteworthy) require some response from the
system. Signals represent asynchronous mechanisms used to communicate certain types of events within the system.
If there are existing elements from previous passes over the design or from selected frameworks or other reusable
elements, those should be reused whenever possible.
Having identified the elements, each should be given a meaningful name. Each element should also have a
description so that the team members that work together to refine the design and implement from the
design will understand the role the element will play.
Based on the above, this identification of elements could be applied a number of times in designing just one
part of the system. While there is no one correct strategy for multiple passes, they could be done at a
coarse-grained level and then a fine-grained level or at an analysis (abstract) level and then a design level.
Behavior of Elements
To identify the behavior of the elements, walk through scenarios assigning behavior to the appropriate
collaborating participant. If a particular usage of the system or subsystem can have multiple possible
outcomes or variant sequences, cover enough scenarios to ensure that the design is robust enough to support the
necessary possibilities.
When assigning behavior to elements, consider applying the Entity-Control-Boundary Pattern or other patterns identified in the
architecture.
Behavior can be represented as a simple statement of responsibility or it can be a detailed operation
specification. Use the appropriate level of detail to communicate important design decisions while giving
the freedom to make appropriate implementation decisions as those tasks ensue.
Behavior must be understood as a responsibility on an element, and as an interaction between two elements in the
context of some broader behavior of the system or subsystem. The latter part of this will lead the developer to
identify relationships needed between the elements.
Avoid too much identification of behavior solely from the perspective of domain modeling. Only include
behavior that is really needed, behavior identified by walking through required scenarios of system usage.
Design Element Relationships
The relationships between the elements necessary for the behavior must be designed. This can simply be the
identification of the ability to traverse from one element to another or a need to manage an association
between the elements.
More detailed design can be performed on the relationships as appropriate. This can include optionality,
multiplicity, whether the relationship is a simple dependency or managed association, etc. These decisions that drive
implementation details are best made at the design level when it is easier to see how all the pieces fit
together.
As with the behavior discussion above, avoid defining too many relationships based on relationships in the application
domain. Only include the relationships that are really needed based on the requirements. On the other hand,
a combination of requirements knowledge and domain knowledge can lead to some detailed decisions on the relationships
such as optionality and multiplicity.
Refine Design
Having identified a design including a set of collaborating elements with the behavior and relationships
robust enough to handle the requirements under consideration, the design can be improved and transformed into an
implementable system through refinement.
The visibility of each operation should be selected to be as restrictive as possible. Based on walking
through the scenario it should be clear which operations must be available to other elements in the design and
which can be considered private behavior inside the element that has the operation. Minimizing the number of
public operations creates a more maintainable and understandable design.
With respect to parameters, the return value, and a description of how it will go about performing the behavior,
operations can be detailed at a lower level that drives the actual implementation or that detail might be left to
be handled when writing the code.
Data attributes can be identified based on information needed to support behavior or based on additional requirements
such as information to be presented to the user or transmitted to another system. Avoid indiscriminate domain
analysis; there might be a great deal of data in the domain that is not needed to support the requirements. Data
attributes can simply be identified or they can be designed in detail with attribute types, initial values, and
constraints. Decide on the visibility of the data attribute; operations to access and update the data can be added or
deferred to implementation.
Generalization and interfaces can be applied to simplify or otherwise improve the design. Ensure that the
usage of these techniques actually improves the design rather than muddling it with complexity. For example,
common behavior can be factored into a parent class via generalization or out to a helper class via delegation; the
latter solution can be more understandable and maintainable as generalization is an inflexible relationship.
The refinement of any portion of the design could include another pass through the design process. One might find
that what was initially identified as a single behavior on an element warrants a detailed walkthrough of the
collaborating elements to realize that behavior.
When updating an existing design
– especially one that has had portions already implemented
– apply Refactoring to ensure that the improved design continues to perform as expected.
Organization of Elements (package-level)
In a design of any notable size, the elements must be organized into packages. Assign the elements to
existing or new packages and ensure that the visibility relationships between the packages support
the navigability required between the elements. Decide whether each element should be visible to elements
outside its package.
When structuring the design into packages, consider Layering and other patterns. Though all design work must conform to
existing architectural decisions, the allocation of elements to packages and possible updates to package
visibility is an area of significant architectural concern. The developer should collaborate with the
architect to ensure that package-level decisions are in accordance with the rest of the architecture.
This guideline first talks about the identification and design of the elements and then about organizing
the elements into packages. This is not a strict order of events. There is nothing wrong with
identifying a package structure for the system and then populating that structure with identified elements as
long as the actual elements identified are allowed to influence the resulting package structure.
Reviewing the Design
Design is best done collaboratively as it is a problem-solving activity with a range parts and perspectives.
There should be a constant level of review to ensure that the decisions make sense within the area being designed and
in the design of the system overall. There also might be occasions where the review of some area of design is
reviewed by a set of interested or knowledgeable parties such as the architect who will verify that the design conforms
to some architectural decision or a developer who will be expected to implement the design.
The design should be examined to ensure that it follows heuristics of quality design such as loose coupling and high
cohesion. Responsibilities should be appropriately distributed to elements such that there are no elements
with too much responsibility and no elements are left without any responsibilities. The design should be able to
clearly communicate the design decisions while not delving into concerns best dealt with during implementation of
code.
Ensure the design follows any project-specific guidelines and conforms to the architecture.
Modifications to the design to improve it based on issues identified in reviewing it should apply Refactoring to ensure that the design and any existing implementation of the design
continues to fulfill its responsibilities.
Relationship of Design to Architecture
This guideline remarks on conforming to the architecture in various ways; it is written as though one is designing
within a pre-existing architecture. Though projects will often have pre-existing architectures available, a
particular architecture is the result of design activities. Therefore, in addition to discussing conformance to
some existing architecture, one must also consider the creation of the architecture and updates and improvements to the
architecture based on the work of design.
|