The goal of this article is to provide a good understanding of what use case and sequence diagrams are, how they are properly created, and how they can influence a design. This article will cover the standard parts of each diagram and how they fit together, at the proper level, to create a diagram that will help developers and users.
Throughout this tutorial we will focus on the File Validator component, using that component to show the right and wrong ways to create the use case and sequence diagrams.
Use Case Diagrams
A use case diagram is a simple diagram that describes what can be done with the component. It should describe, in simple terms, all major functionality that can be accomplished with a component
Use Case Items
A use case is made up of the following items:
Determining The Proper Use Cases
Determining the proper use cases is probably the hardest part of generating the use case diagram. There are a lot of mistakes that can be made when figuring out what use cases to include.
The main step in getting the use cases right is to do the Use Case diagram before you do anything else in the design. This ensures that the use case isn't affected by the designer's vision of the implementation, which can lead to errors.
In a TopCoder design competition, the easiest way to determine the use cases to include is to first go through the requirements, pulling out required operations that the user should be able to accomplish with the component. If it is a functionality requirement, it should be a use case and also easily accessible in the API.
For the File Validator component, the following required operations can be taken from the requirements specification given:
The first four use cases pulled from the requirements — Validate string, Validate stream, Validate byte array, and Validate file path — can be all grouped under a "Validate file" use case. There was the option of making each concrete type that the component could validate (string, stream, byte array, file path) its own concrete use case, but I determined that doing that would lead to use cases that were too "low level" (see below for more on identifying proper level). Although it was decided not to include those use cases, a note was added explaining that the use case covered those possibilities, as it is a requirement of the design that the use cases cover the requirements mentioned in the requirement specification.
The first thing to do when defining the use case diagram is to determine the actor. In this case, we know the component is a library that can be used by TopCoder for many different projects, but will always be part of a much larger program. Since this component isn't used directly by a user, only as a library, we can determine that the actor should be called "Application," representing the larger application that will use this component.
Next, we look for any abstract use cases that may be defined. The abstract use case is easy to glean from the use cases pulled from the requirements. Notice that both the XSD and Magic Numbers validation both start with text similar to "Validate a file." This makes it obvious that they both describe the same sort of functionality that could be described by some abstract use case, which in this case we call "Validate file."
The next step is to add any concrete use cases. For this component, we add the "Validate a file using Magic Numbers," and "Validate an XML file using an XSD file."
Finally, we add the relationships. The first one is from the user to the abstract use case, and the next is the "extend" relationships from the two concrete use cases to the "Validate file" abstract use case.
After adding the note about the different input types allowed, we get a use case diagram that looks like this:
This diagram succinctly and accurately describes the functionality of the component at a high level. The user can see, just by looking at this diagram and nothing else in the component, what functionality is available.
If I had stopped here with the use case diagram, that would have been fine, and I would have had little to no trouble during review. My problem was that I decided to add some additional functionality, in the form of another XML validation scheme: DTD. This would allow me to get extra points for additional functionality, but these extra points were diminished because of how I chose to show that new functionality in the use case diagram:
This may look fine at first, but notice that now we have two concrete use cases that start with the same "Validate XML File" string. This is a perfect place for another abstract use case, but I missed that before submitting. Every one of the three reviewers picked up on the problem, and it cost me some points. This is why it is best to solidify your use case diagram before you start anything else. By constantly coming back and changing the diagram, you introduce the possibility of errors.
After final fixes, the final use case diagram looked like this:
Getting The Right "Level"
Determining if a use case has the right level - neither too high nor too low - is a tricky thing to do. First we have to know what is meant by use case "level." The level of a use case is really how well it defines what the functionality of the component is to the user. Determining if a use case is at the right level is really a matter of asking yourself two questions:
The purpose of the second question is to determine if the use case is too low level. The purpose of the use case diagram is to explain to the user what functionality is available in a given component, but the user doesn't need to know how the functionality is obtained.
For example, if the use case diagram I had come up with for the File Validator component had looked like this:
We could determine that a number of the new use cases were too low level, as they would fail the second question test. The user shouldn't care at this point that we use the XmlFileValidator, or that they need to set the FileType to validate against, or how the Magic Numbers component is used to validate the file type.
How The Use Case Diagram Affects The Design
The use case diagram should affect the overall design of the API, not the other way around. To ensure that the API is coherent and simple for the user, each concrete use case should be easily accessed through a method or two in the API. The easiest way to do this is to map each concrete use case to a method in the API, named similar to the use case if possible.
The areas in the use case diagram that are abstract should be represented by pluggable features in the design, either through an abstract class or an interface. Since the abstract use case defines similar behavior for use cases, and abstract classes and interfaces define similar behavior for classes, it should be straightforward to map the API and the use cases.
Going back to the File Validator component again, it is fairly obvious what the API should look like, just by looking at the use cases.
Since "Validate File" is abstract, it should be represented by an abstract class or an interface in the class diagram, if possible. In this component, the use case maps directly to the FileValidator class. The FileValidator class handles all the different file formats allowed — from string, to byte array, to path, and to stream, and the conversions between them — allowing the subclasses to only need to override a single method that takes a stream parameter. "Validate XML File" is also abstract, and it maps to the XmlFileValidator class. The XmlFileValidator class aggregates the actual validation functionality, so all the subclasses need to do is pass the parent class some parameters. The three concrete use cases can be seen mapping to the three concrete classes in the component. In this way, the component is coherent and should be easily used and understood by the user.
If you do the use cases first and allow the final use case diagram to affect the overall outcome of the API, you should end up with a component that will be coherent, consistent, and easy for the user to pick up and use.
Use Case Dos And Don'ts
A sequence diagram is a diagram that shows the flow of data through an operation in the component. Normally a sequence diagram covers all the interactions that happen on a particular method call in the component.
Sequence diagrams are easier to produce correctly than a use case diagram. They are more time consuming, mainly because you generally need to do at least a couple for most components, but generating them is straightforward.
Sequence Diagram Items
Sequence diagrams are made up of two items, in a two dimensional grid. Along the X-axis are all the objects that are going to be used in the diagram. They should be named after the type they represent, like IDictionary or XmlFileValidator. Along the Y-axis run the messages between these items, representing all the different calls in the diagram.
The messages run on "swimlanes" between two different objects. The actual call is represented by a solid arrow, and the return value is represented by a dashed arrow going back to the caller. Each message in the diagram needs to have a short description, as well as the name of the call being made. The return arrows just need to have "Return" as the description and the type returned as the message. The message returned should be "void" if the call doesn't have a return value. A create call is special and needs to be marked with the "<<create>>" stereotype.
Picking The Interactions To Show In Sequence Diagrams
Picking the correct interactions in the component to document as sequence diagrams is the most difficult part of the sequence diagram deliverable in TopCoder components. The TopCoder review guidelines for sequence diagrams states:
"Interactions involving five or more objects (both system and/or component) are considered complicated. Designers may include SDs with less than five objects, but they are not required to do so."
This helps a bit when determining what to diagram. Keep in mind that the number of objects is not just those in the component, but all those actually used in the operation, like all the SQL types (connection, command, reader), or all the XML types (XmlDocument, XmlReader, XmlNode, XmlElement).
Generally speaking, if an operation involves another TopCoder component — like ConnectionFactory or IDGenerator — it should be shown as a sequence diagram. The exception to this rule would be the Configuration Manager. Using the Configuration Manager just to read in simple properties for a class normally doesn't need to be documented in a sequence diagram. If, on the other hand, the Configuration Manager loading involves creation of more complex objects from the component, or moderate computation, it should be documented with a sequence diagram.
The biggest mistake I see when doing reviews is that designers don't include sequence diagrams they think are self-explanatory or easy. The normal operations skipped are SQL operations, like INSERT and UPDATE, as well as XML parsing operations. Keep in mind that the current scorecard doesn't allow a lot of leeway in this area, so it is a good idea to include SQL and XML interactions. This also helps clarify areas in the component, especially things like SQL connection handling.
Diagramming The Interactions
Actually generating the diagram isn't hard, but it can be time consuming. Optimally, the sequence diagram should show all interactions in a particular call, down to collection types like IDictionary and List. In practice, this can lead to large diagrams, but the more information available the better. Sequence diagrams are not approximations or overviews, they are meant to be an accurate representation of all the interactions in a call.
The sequence diagrams should normally start at the left side of the diagram with an "Application" object that represents the external caller. That object has a message from it to an object in the component, representing the call made.
This is the average case for sequence diagrams, but sometimes internal interactions that need to be documented could originate from an object inside the component. After the first interaction, it is just a matter of accurately showing the flow of data in the call, in as much detail as possible. Make sure to include objects not housed in your design, especially TopCoder objects from other components used.
Notice in the above diagram we show the FileValidator calling another instance of the FileValidator object. This is my way of showing internal interactions between the same instance of a class, but you can optionally use the loopback arrow, where the message loops back to the originating object.
Loops are extra pieces that can make a big difference in a diagram. Currently there are two ways of doing loops — you can use the loop construct in Poseidon 4.1.2, or you can do it the "old" way by drawing a rectangle around the area that is repeated. I prefer the "old" rectangle way, as you are allowed more documentation in the rectangle describing what the looping actually accomplishes. An example of looping can be seen in this diagram:
Here are two possible shortcuts you can take when doing sequence diagrams. These aren't officially sanctioned, so use at your own risk. I have found these to work well for me, in moderation.
Use notes to your advantage
A lot of times in components you will see that two or more interactions are similar, but still considered "complicated." In these cases you could save some time by doing one diagram well, then adding a note explaining the other interactions and how they differ from the main diagram. Only use this if the interactions are very similar, and the differences can be described in a sentence or two.
Show shortcuts to the interactions
Use this only in moderation, and only carefully. I have found that by using loops and other things you can show shortcuts to interactions. The main place I use this is XML parsing, where we read more than 3 items from an XML node. You can group these all into one call by adding a loop around the read and explaining in the loop that this interaction is repeated for "items 2-x," whatever they may be. Note that you still need to show all interactions; you are just grouping some together to save space.
Sequence Diagram Dos And Don'ts
One thing not really covered in this tutorial is proper coloring of the diagrams. You can review the sample use case and sequence diagrams for a good explanation of the colors used. The sequence diagram generation has changed with Poseidon 4.1.2, so you may need to be creative in getting them correct, at least until new samples are available for the updated versions.
Hopefully this tutorial gives you a good understanding about what use case and sequence diagrams are, and how they can be done correctly. Keep in mind these are just guidelines, not definitive rules - and always be prepared to appeal your decisions to reviewers.