Activity:
|
Purpose
|
|
Role: Designer | |
Frequency: Once per iteration, for a set of Artifact: Design Use-Case Realizations. | |
Steps | |
Input Artifacts: | Resulting Artifacts: |
Tool Mentors:
|
|
More Information: | |
Workflow Details: |
The behavior of a system can be described using a number of techniques - collaborations or interactions. This activity describes the use of interactions, specifically sequence diagrams, to describe the behavior of the system. Sequence diagrams are most useful where the behavior of the system or subsystem can be primarily described by synchronous messaging. Asynchronous messaging, especially in event-driven systems, is often more easily described in terms of state machines and collaborations, allowing a compact way of defining possible interactions between objects.
Asynchronous messages play an important role in real-time or reactive systems, and are used for communication between instances of Artifact: Capsules.
The Artifact: Design Use-Case Realization provides a way to trace behavior in the Design Model back to the Use-Case Model, and it organizes collaborations in the Design Model around the Use-Case concept.
Create a Design Use-Case Realization in the Design Model for each Use Case to be designed. The name for the Design Use-Case Realization should be the same as the associated Use Case, and a "realizes" relationship should be established from the use-case realization to its associated use case.
For each use-case realization, you should illustrate the interactions between its participating design objects by creating one or more sequence diagrams. Early versions of these may have been created during Activity: Use-Case Analysis. Such "analysis versions" of the use-case realizations describe interactions between analysis classes. They need to be evolved to describe interactions between design elements.
Updating the sequence diagrams involves the following steps:
Note that these are system-level sequence diagrams, which show how instances of top level design elements (typically subsystems and subsystem interfaces) interact. Sequence diagrams showing the internal design of subsystems are produced separately, as part of Activity: Subsystem Design.
Note that active object interactions are typically described using specification collaborations and state machines. They would be used here to show how messages can be sent to active objects by other elements in the system in a larger use-case realization. In typical usage, active objects are encapsulated within subsystems for the purpose of this activity, such that the use-case realization consists of a set of interacting subsystems. The interactions define the responsibilities and interfaces of the subsystems. Within the subsystems, active objects represent concurrent threads of execution. The subsystems allow work to be divided between development teams, with the interfaces serving as the formal contracts between the teams.
For real-time systems, you will use Artifact: Capsules to represent the active objects.
A minor note on showing messages emanating from subsystems: restricting messages only to interfaces reduces coupling between model elements and improves the resiliency of the design. Where possible, you should try to achieve this, and in cases where there are messages emanating from subsystems to non-interface model elements, you should look for opportunities to change these to messages to interfaces to improve decoupling in the model.
You document the use-case behavior performed by the objects in a sequence diagram.
When you have distributed behavior among the objects, you should consider how the flow will be controlled. You found the objects by assuming they would interact a certain way in the use-case realization, and have a certain role. As you distribute behavior, you can begin to test those assumptions. In some parts of the flow, you might want to use a decentralized structure; in others, you might prefer a centralized structure. For definitions of these variants and recommendations on when to use the two types of structure, see Guidelines: Sequence Diagrams.
You might need new objects at this point, for example if you are using a centralized structure and need a new object to control the flow. Remember that any object you add to the design model must fulfill the requirements made on the object model.
During Activity: Architectural Analysis, analysis mechanisms were identified. During Activity: Identify Design Mechanisms, analysis mechanisms re refined into design mechanisms, the mapping from the analysis mechanisms to the design mechanisms is captured in the Software Architecture Document, and the design mechanisms are documented in the Project Specific Guidelines.
During this activity, Use-Case Design, any applicable design mechanisms are
incorporated into the use-case realizations. The Designer surveys the
available design mechanisms and determines those that apply to the use-case
realization being developed, working within the recommendations and guidelines
documented in the Software Architecture Document and the Design
Guidelines.
Note: Applicable design mechanism may have been identified in Activity:
Use-Case Analysis, during which analysis classes may have been "tagged"
with a particular analysis mechanism, indicating that a particular piece of
functionality needed to be handled in the design. In such a case, the
applicable design mechanisms are those associated with the analysis mechanisms
that analysis classes participating in the use-case realization were tagged
with
The Designer incorporates the applicable design mechanisms into the use-case realizations by including the necessary design elements and design element interactions into the use-case realizations following the rules of use documented in the Design Guidelines.
You should describe each flow variant in a separate sequence diagram. Sequence diagrams are generally preferable to communication diagram as they tend to be easier to read when the diagram must contain the level of detail we typically want in when designing the system.
Start with describing the basic flow, which is the most common or most important flow of events. Then describe variants such as exceptional flows. You do not have to describe all the flows of events, as long as you employ and exemplify all operations of the participating objects. Given this, very trivial flows can be omitted, such as those that concern only one object.
Study the use case to see if there are flow variants other than those already described in requirements capture and analysis, for example, those that depend on implementation. As you identify new flows, describe each one in a sequence diagram. Examples of exceptional flows include the following.
You can describe an alternative path of a flow as an optional flow instead of as a variant. The following list includes two examples of optional flows.
If you want an optional flow, or any complex sub-flow, to be especially noticeable, use a separate sequence diagram. Each separate sequence diagram should be referred to from the sequence diagram for the main flow of events using scripts, margin text or notes to indicate where the optional or sub-flow behavior occurs.
In cases where the optional or exceptional flow behavior could occur anywhere, for example behavior which executes when a particular event occurs, the sequence diagram for the main flow of events should be annotated to indicate that when the event occurs, the behavior described in the optional/exceptional sequence diagram will be executed. Alternately, if there is significant event-driven behavior, consider using statechart diagrams to describe the behavior of the system. For more information, see Guidelines: Statechart Diagram.
When a use case is realized, the flow of events is usually described in terms of the executing objects, i.e. as interaction between design objects. To simplify diagrams and to identify re-usable behavior, there may be a need to encapsulate a sub-flow of events within a subsystem. When this is done, large subsections of the sequence diagram are replaced with a single message to the subsystem. Within the subsystem, a separate sequence diagram may illustrate the internal interactions within the subsystem that provide the required behavior (for more information, see Activity: Subsystem Design).
Sub-sequences of messages within sequence diagrams should be encapsulated within a subsystem when:
A use-case realization can be described, if necessary, at several levels in the subsystem hierarchy. The lifelines in the middle diagram represent subsystems; the interactions in the circles represent the internal interaction of subsystem members in response to the message.
The advantages of this approach are:
Example:
Consider the following sequence diagram, which is part of a realization of the Local Call use case:
In this diagram, the gray classes belong to a Network Handling subsystem; the other classes belong to a Subscriber Handling subsystem. This implies that this is a multi-subsystem sequence diagram, i.e. a diagram where all the objects that participate in the flow of events are included, regardless of whether their classes lie in different subsystems or not.
As an alternative, we can show invocation of behavior on the Network Handling subsystem, and the exercise of a particular interface on that subsystem. Let's assume that the Network Handling subsystem provides an ICoordinator interface, which is used by the Subscriber Handling subsystem:
The ICoordinator interface is realized by the Coordinator class within Network Handling. Given this, we can use the Network Handling subsystem itself and its ICoordinator interface in the sequence diagram, instead of instances of classes within Network Handling:
Note that the Coordinator, Digit Information, and Network class instances are substituted by their containing subsystem. All calls to the subsystem are instead done via the ICoordinator interface.
In order to achieve true substitutability of subsystems realizing the same interface, only their interface should be visible in interactions (and in diagrams in general); otherwise the interactions (or diagrams) need to be changed when subsystems are substituted with each other.
Example:
We can include only the ICoordinator interface, but not its providing subsystem, in a sequence diagram:
Sending a message to an interface lifeline means that any subsystem which realizes the interface can be substituted for the interface in the diagram. Note that the ICoordinator interface lifeline does not have messages going out from it, since different subsystems realizing the interface might send different messages. However, if you want to describe what messages should be sent (or are allowed to be sent) from any subsystem realizing the interface, such messages can go out from the interface lifeline.
In some cases it can be appropriate to develop a subsystem more or less independently and in parallel with the development of other subsystems. To achieve this, we must first find subsystem dependencies by identifying the interfaces between them.
The work can be done as follows:
You can also choose whether to arrange the sequence diagrams in term of subsystems
or in terms of their interfaces only. In some projects, it might even be necessary
to implement the classes providing the interfaces before you continue with the
rest of the modeling.
The whole goal of the object-oriented paradigm is to encapsulate implementation details. Therefore, with respect to persistence, we would like to have a persistent object look just like a transient object. We should not have to be aware that the object is persistent, or treat it any differently than we would any other object. At least that's the goal.
In practice, there might be times when the application needs to control various aspects of persistence:
There are two cases to be concerned with here: the initial time the object is written to the persistent object store, and subsequent times when the application wants to update the persistent object store with a change to the object.
In either case, the specific mechanism depends on the operations supported by the persistence framework. Generally, the mechanism used is to send a message to the persistence framework to create the persistent object. Once an object is persistent, the persistence framework is smart enough to detect subsequent changes to the persistent object and write them to the persistent object store when necessary (usually when a transaction is committed).
An example of a persistent object being created is shown below:
The object PersistenceMgr is an instance of VBOS, a persistence framework. The OrderCoordinator creates a persistent Order by sending it as the argument to a 'createPersistentObject' message to the PersistenceMgr.
It is generally not necessary to explicitly model this unless it is important to know that the object is being explicitly stored at a specific point in some sequence of events. If subsequent operations need to query the object, the object must exist in the database, and therefore it is important to know that the object will exist there.
Retrieval of objects from the persistent object store is necessary before the application can send messages to that object. Recall that work in an object-oriented system is performed by sending messages to objects. But if the object that you want to send a message to is in the database but not yet in memory, you have a problem: you cannot send a message to something which does not yet exist!
In short, you need to send a message to an object that knows how to query the database, retrieve the correct object, and instantiate it. Then, and only then, can you send the original message you originally intended. The object that instantiates a persistent object is sometimes called a factory object. A factory object is responsible for creating instances of objects, including persistent objects. Given a query, the factory could be designed to return a set of one or more objects which match the query.
Generally objects are richly connected to one another through their associations, so it is usually only necessary to retrieve the root object in an object graph; the rest are essentially transparently 'pulled' out of the database by their associations with the root object. (A good persistence mechanism is smart about this: it only retrieves objects when they are needed; otherwise, we might end up trying to instantiate a large number of objects needlessly. Retrieving objects before they are needed is one of the main performance problems caused by simplistic persistence mechanisms.)
The following example shows how object retrieval from the persistent object store can be modeled. In an actual sequence diagram, the DBMS would not be shown, as this should be encapsulated in the factory object.
The problem with persistent objects is, well, they persist! Unlike transient objects which simply disappear when the process that created them dies, persistent objects exist until they are explicitly deleted. So it's important to delete the object when it's no longer being used.
Trouble is, this is hard to determine. Just because one application is done with an object does not mean that all applications, present and future, are done. And because objects can and do have associations that even they don't know about, it is not always easy to figure out if it is okay to delete an object.
In design, this can be represented semantically using state charts: when the object reaches the end state, it can be said to be released. Developers responsible for implementing persistent classes can then use the state chart information to invoke the appropriate persistence mechanism behavior to release the object. The responsibility of the Designer of the use-case realization is to invoke the appropriate operations to cause the object to reach its end state when it is appropriate for the object to be deleted.
If an object is richly connected to other objects, it might be difficult to determine whether the object can be deleted. Since a factory object knows about the structure of the object as well as the objects to which it is connected, it is often useful to charge the factory object for a class with the responsibility of determining whether a particular instance can be deleted. The persistence framework can also provide support for this capability.
Transactions define a set of operation invocations which are atomic; they are either all performed, or none of them are performed. In the context of persistence, a transaction defines a set of changes to a set of objects which are either all performed or none are performed. Transactions provide consistency, ensuring that sets of objects move from one consistent state to another.
There are several options for showing transactions in Use Case Realizations:
Representing transaction boundaries using textual annotations.
A sequence diagram showing explicit messages to start and stop transactions.
If all operations specified in a transaction cannot be performed (usually because an error occurred), the transaction is aborted, and all changes made during the transaction are reversed. Anticipated error conditions often represent exceptional flows of events in use cases. In other cases, error conditions occur because of some failure in the system. Error conditions should be documented in interactions was well. Simple errors and exceptions can be shown in the interaction where they occur; complex errors and exception may require their own interactions.
Failure modes of specific objects can be shown on state charts. Conditional flow of control handling of these failure modes can be shown in the interaction in which the error or exception occurs.
Concurrency describes the control of access to critical system resources in the course of a transaction. In order to keep the system in a consistent state, a transaction may require that it have exclusive access to certain key resources in the system. The exclusivity may include the ability to read a set of objects, write a set of objects, or both read and write a set of objects.
Let's look at a simple example of why we might need to restrict access to a set of objects. Let's say we a running a simple order entry system. People call-in to place orders, and in turn we process the orders and ship the orders. We can view the order as a kind of transaction.
To illustrate the need for concurrency control, let's say I call in to order a new pair of hiking boots. When the order is entered into the system, it checks to see if the hiking boots I want, in the correct size, are in inventory. If they are, we want to reserve that pair, so that no one else can purchase them before the order can be shipped out. Once the order is shipped, the boots are removed from inventory.
During the period between when the order is placed and when it ships, the boots are in a special state—they are in inventory, but they are "committed" to my order. If my order gets canceled for some reason (I change my mind, or my credit card has expired), the boots get returned to inventory. Once the order is shipped, we will assume that our little company does not want to keep a record that it once had the boots.
The goal of concurrency, like transactions, is to ensure that the system moves from one consistent state to another. In addition, concurrency strives to ensure that a transaction has all the resources it needs to complete its work. Concurrency control may be implemented in a number of different ways, including resource locking, semaphores, shared memory latches, and private workspaces.
In an object-oriented system, it is difficult to tell from just the message patterns whether a particular message might cause a state change on an object. Also, different implementations may obviate the need to restrict access to certain types of resources; for example, some implementations provide each transaction with its own view of the state of the system at the beginning of the transaction. In this case, other processes may change the state of and object without affecting the 'view' of any other executing transactions.
To avoid constraining the implementation, in design we simply want to indicate the resources to which the transaction must have exclusive access. Using our earlier example, we want to indicate that we need exclusive access to the boots that were ordered. A simple alternative is to annotate the description of the message being sent, indicating that the application needs exclusive access to the object. The Implementer then can use this information to determine how best to implement the concurrency requirement. An example sequence diagram showing annotation of which messages require exclusive access is shown below. The assumption is that all locks are released when the transaction is completed.
An example showing annotated access control in a sequence diagram.
The reason for not restricting access to all objects needed in a transaction
is that often only a few objects should have access restrictions; restricting
access to all objects participating in a transaction wastes valuable resources
and could create, rather than prevent, performance bottlenecks.
In the flow of events of the use-case realization you may need to add additional description to the sequence diagrams, in cases where the flow of events is not fully clear from just examining the messages sent between participating objects. Some examples of these cases include cases where timing annotations, notes on conditional behavior, or clarification of operation behavior is needed to make it easier for external observers to read the diagrams.
The flow of events is initially outlined in the Activity: Use-Case Analysis. In this step you refine the flow of events as needed to clarify the sequence diagrams.
Often, the name of the operation is not sufficient to understand why the operation is being performed. Textual notes or scripts in the margin of the diagram may be needed to clarify the sequence diagram. Textual notes and scripts may also be needed to represent control flow such as decision steps, looping, and branching. In addition, textual tags may be needed to correlate extension points in the use case with specific locations in sequence diagrams.
Previous examples within this activity have illustrated a number of different ways of annotating sequence diagrams.
As use cases are realized, you need to unify the identified design classes and subsystems to ensure homogeneity and consistency in the Design Model.
Points to consider:
You should check the design model at this stage to verify that your work is headed in the right direction. There is no need to review the model in detail, but you should consider the Checkpoints for the Design Model while you are working on it.
See especially checkpoints for use-case realization in the Activity: Review the Design.
You can use a proxy class to represent the subsystem on sequence diagrams. This proxy class is contained within the subsystem and is used to represent the subsystem in diagrams which do not support the direct use of packages and subsystems as behavioral elements. Use the proxy class in cases where you want to show that a specific subsystem responds to a message. In this case, you can show messages being sent from the subsystem proxy to other objects.
Refer to Differences Between UML 1.x
and UML 2.0 for more information.
Rational Unified Process |