Roles and Activities > Designer > Use-Case Design

Purpose
  • To refine use-case realizations in terms of interactions.
  • To refine requirements on the operations of design classes.
  • To refine requirements on the operations of subsystems and/or their interfaces.
  • To refine requirements on the operations of capsules.
Steps
Input Artifacts: Resulting Artifacts:
Role: Designer

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.

Describe Interactions Between Design Objects To top of page

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.

Updating the sequence diagrams involves the following steps:

  • Identify each object that participates in the flow of the use case.

  • Represent each participating object in a sequence diagram. Make a lifeline for each participating object in the sequence diagram:
    • You may use the interfaces realized by the subsystem. This is preferred in cases where you wish to show that any model element which realizes the same interface may be used in place of the interface. If you choose to show interfaces on the sequence diagram, be aware that you will want to ensure that no messages are sent from the interface to other objects. The reason for this is that interfaces completely encapsulate the internal realization of their operations. Therefore, we cannot be certain that all model elements which realize the interface will in fact actually be designed the same way. So on sequence diagrams no messages should be shown being sent from interfaces.


  • Represent the interaction that takes place with actors. Represent each actor instance and external object that the participating objects interacts with by a lifeline in the sequence diagram.

  • Illustrate the message sending between participating objects. The flow of events begins at the top of the diagram and continues downward, indicating a vertical chronological axis. Illustrate the message sending between objects by creating messages (arrows) between the lifelines. The name of a message should be the name of the operation invoked by the message. In the early stages of design, not many operations will be assigned to the objects, so you may have to leave this information out and give the message a temporary name; such messages are said to be "unassigned." Later, when you have found more of the participating objects' operations, you should update the sequence diagram by "assigning" the messages with these operations.

  • Describe what an object does when it receives a message. This is done by attaching a script to the corresponding message. Place these scripts in the margin of the diagram. Use either structured text or pseudocode. If you use pseudocode, be sure to use constructs in the implementation language so that the implementation of the corresponding operations will be easier. When the person responsible for an object's class assigns and defines its operations, the object's scripts will provide a basis for that work.

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 may want to use a decentralized structure; in others, you may prefer a centralized structure.

You may 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.

Handle All Variants of the Flow of Events

You should describe each flow variant in a separate sequence diagram. Sequence diagrams are generally preferable to collaboration diagrams as they tend to be easier to read when the diagram must contain the level of detail we typically want 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.

  • Error handling. If an interface reports that an error has occurred in its communication with some external system, for example, the use case should deal with this. A possible solution is to open a new communication route.
  • Time-out handling. If the user does not reply within a certain period, the use case should take some special measures.
  • Handling of erroneous input to the objects that participate in the use case. Errors like this might stem from incorrect user input.

Handle Optional Parts of the Use Case

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.

  • By sending a signal, the actor decides-from a number of options-what the use case is to do next. The use case has asked the actor to answer yes or no to a question, for example, or provided the actor with a variety of functions the system can perform in the use case's current state.
  • The flow path varies depending on the value of stored attributes or relationships. The subsequent flow of events depends on the type of data to be processed.

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.

Simplify Sequence Diagrams using Subsystems (optional) To top of page

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 .

Sub-sequences of messages within sequence diagrams should be encapsulated within a subsystem when:

  • The sub-sequence occurs repeatedly in different use-case realizations; that is, the same (or similar) messages are sent to the same (or similar) objects, providing the same end result. The phrase 'similar' is used because some design work may be needed to make the behavior reusable.
  • The sub-sequence occurs in only one use-case realization, but it is expected to be performed repeatedly in future iterations, or in similar systems in the future. The behavior may make a good reusable component.
  • The sub-sequence occurs in only one use-case realization, it is complex but easily encapsulated, needs to be the responsibility of one person or a team, and provides a well-defined result. In these kinds of situations, the complex behavior usually requires special technical knowledge, or special domain knowledge, and as a result is well-suited to encapsulating it within a subsystem.
  • The sub-sequence is determined to be encapsulated within a component in the implementation model. In this case, a subsystem is the appropriate representation for the component within the design model.

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:

  • Use-case realizations become less cluttered, especially if the internal design of some subsystems is complex.
  • Use-case realizations can be created before the internal designs of subsystems are created.
  • Use-case realizations become more generic and easy to change, especially if a subsystem needs to be substituted with another subsystem.

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.

Showing Interfaces on Lifelines

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 may 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.

Describe Persistence-Related Behaviors To top of page

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 may be times when the application needs to control various aspects of persistence:

  • when persistent objects are read and written
  • when persistent objects are deleted
  • how transactions are managed
  • how locking and concurrency control is achieved

Writing Persistent Objects To top of page

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.

Reading Persistent Objects To top of page

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.

Deleting Persistent Objects To top of page

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 may 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 may also provide support for this capability.

Modeling Transactions To top of page

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:

  • Textually. Using scripts in the margin of the sequence diagram, transaction boundaries can be documented as shown below. This method is simple, and allows any number of mechanisms to be used to implement the transaction.

Representing transaction boundaries using textual annotations.

  • Using Explicit Messages. If the transaction management mechanism being used uses explicit messages to begin and end transactions, these messages can be shown explicitly in the sequence diagram, as shown below:

A sequence diagram showing explicit messages to start and stop transactions.

Handling Error Conditions To top of page

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 as 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.

Handling Concurrency Control To top of page

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 an 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.

Refine the Flow of Events Description To top of page

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.

Unify Classes and Subsystems To top of page

As use cases are realized, you need to unify the identified classes and subsystems to ensure homogeneity and consistency in the model.

Points to consider:

  • Names of model elements should describe their function.
  • Avoid similar names and synonyms because they make it difficult to distinguish between model elements.
  • Merge model elements that define similar behavior, or that represent the same phenomenon.
  • Merge entity classes that represent the same concept or have the same attributes, even if their defined behavior is different.
  • Use inheritance to abstract model elements, which tends to make the model more robust.
  • When updating a model element, also update the corresponding flow of events description of the use-case realizations.

Evaluate Your Results To top of page

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.