A series of posts providing proven guidance for developing ASP.NET MVC applications from idea to well-designed implementation.
Day 3 – Define the Domain Design Model
Objective of the Day
Transform the actor/system interaction diagrams - from Day 1 - and the domain conceptual model - from Day 2 - into a design model, which will be used as the basis for implementation. The design model will include two artifacts:
- Class Diagram: A class diagram extends the domain conceptual model to include data types, methods, and supporting classes necessary for implementation.
- Sequence Diagrams: Sequence diagrams express the interactions among classes and the messages invoking, and responding to, those interactions, accordingly.
- Actor/System interaction diagrams
- Domain conceptual model
Designing the domain conceptual model, and evolving that into the domain design model represents the transition from business-oriented to implementation-oriented requirements. While there are two outputs from this process, class diagrams and sequence diagrams, they are defined in parallel with each other, each contributing to the design of the other.
Transforming the conceptual model into a design model is most effectively done iteratively. This does not mean that each refinement need take a two-week iteration to complete. Indeed, each iterative refinement may only take 30 minutes or so to complete. Tackling this transformation iteratively allows you to learn from each pass, applying new knowledge to the class diagram and to the sequence diagrams in successive passes.
Furthermore, you need not tackle the entire domain model when defining the design model; only focus on the scope and level of detail that is immediately useful.
The steps involved with each elaboration iteration, for defining/refining the class diagram and sequence diagrams, are as follows:
- Identify the bounded context which will be the focus of design model elaboration. (The bounded context need not be strictly adhered to, but should be identified to assist in focusing the elaboration effort.)
- Select one or more high priority User Stories (or Use Cases) which are representative of the bounded context and, preferably, are of higher risk.
- Refine (or initially define) the class diagram, focusing on classes, attributes, and associations within the context of the User Stories.
- For each User Story, refine (or initially define) sequence diagrams illustrating the behavior and interactions of each class involved.
- Update the class diagram to reflect any new or modified classes, behaviors, and/or attributes discovered during the design of sequence diagrams.
When defining sequence diagrams, do not feel constrained to only include classes which are already defined within the class diagram. The exercise of building out the sequence diagrams will quickly lead to the discovery of new classes and behaviors which are not yet a part of the class diagram; that’s one of the points of doing this. Furthermore, do not feel obliged to create a sequence diagram for every User Story or requirement. Sequence diagrams are time consuming to create and are quickly subject to change. Accordingly, I see sequence diagrams as disposable artifacts which need not be maintained after they have been implemented as code.
Sequence diagrams are particularly useful at the following times:
- To illustrate the interactions involved with a complex User Story,
- To illustrate a "boiler-plate activity," such as the interactions involved with the CRUD (create/read/update/delete) of a single domain object, in order to establish a standard practice and to serve as a useful reference for new team members, and
- To tell a more junior developer exactly how you’d like something implemented.
In the final step, when updating the class diagram to reflect new or modified behaviors, use each message, passed between two classes within the sequence diagram, to indicate the need for a respective method on the class from which the message originates. In other words, if a communication is indicated between two classes in the sequence diagram, there should be a respective method (or return result from a message) in the class diagram.
Deciding to what granularity to create the sequence diagrams is a tricky decision. If too little time is spent, then the sequence diagram will provide very little guidance towards implementation. But too much time may result in granularity which is otherwise obvious or clearly assumed. As a rule, continue to break-down the sequence diagram to further levels of granularity until:
- The effort is no longer reducing uncertainty and risk,
- The increased granularity is no longer providing "new" information, or
- The increased granularity is no longer useful to the task at hand (such as providing an initial estimate).
From the perspective of including this practice within an overall project methodology, defining/refining the design model should be roughly performed at the beginning of a project to assist with estimating and to improve understanding of the project. It should also be performed in more detail at the beginning of each development iteration to define/refine the design model for that iteration.
The definitive challenge at this point is figuring out exactly how to transform the conceptual domain model into an appropriate design model describing classes with attributes, associations, and behavior. While sequence diagrams are a tool to visualize interactions among classes and their behaviors, the diagramming technique itself does not provide guidance on how to determine how the classes will interact and what their respective behaviors will be. Doing this correctly is a critical element to ensuring the codebase is well-designed and maintainable.
GRASP patterns provide guidance for assigning responsibility and interactions to objects; GRASP is an acronym for General Responsibility Assignment Software Patterns and were defined to provide guidance for OOA&D. While there are nine GRASP patterns in all, five key GRASP patterns should be mastered and considered continuously in the creation of the design model. What follows are the five patterns, each including the problem that arises when adding behavior to a model, and the guiding principle/solution for meeting that challenge. (Reference Applying UML and Patterns by Craig Larman, from which the following patterns are quoted, for a very well written discussion of these principles and other fundamentals of OOA&D.)
Problem: What is a general principle of assigning responsibilities to objects?
Solution: Assign a responsibility to the information expert - the class that has the information necessary to fulfill the responsibility.
Problem: Who should be responsible for creating a new instance of some class?
Solution: Assign class B the responsibility to create an instance of class A if one or more the following is true:
- B aggregates A objects.
- B contains A objects.
- B records instances of A objects.
- B closely uses A objects.
- B has the initializing data that will be passed to A when it is created (thus B is an Information Export with respect to creating A).
An exception to this is that if the logic required to create class A is of considerable complexity, consider moving the creation logic to a helper class, called a Factory [GoF 1995].
Problem: How to support low dependency, low change impact, and increased reuse? Coupling is how strongly one class is connected to, or has knowledge of, other classes. Highly coupled classes suffer from the following problems:
- Changes in related classes force local changes.
- Harder to understand in isolation.
- Harder to reuse because its use requires the additional presence of the classes on which it is dependent.
Solution: Assign a responsibility so that coupling remains low.
This terse solution is not very helpful; indeed, it’s often simpler to instill low coupling by watching for signs of tight coupling and to refactor, accordingly. Martin Fowler’s Refactoring is by far the best resource available for becoming familiar with these "smells" along with concrete guidance for taking corrective action.
Problem: How to keep complexity manageable? Cohesion is how strongly related the responsibilities of a class are. Classes with low cohesion suffer from the following problems:
- Hard to comprehend.
- Hard to reuse.
- Hard to maintain.
- Delicate and constantly effected by change.
Solution: Assign a responsibility so that cohesion remains high.
Like the solution for Low Coupling, this isn’t immediately useful advice. The point is to aim towards encapsulating related logic together, and to introduce new classes as necessary to continue to do so. Again, Fowler's Refactoring is a tremendous resource for this topic.
Problem: Who should be responsible for handling an input system event?
Solution: Assign the responsibility for receiving or handling a system event message to a class representing one of the following choices:
- Represents the overall system as a Facade [GoF 1995].
- Represents a User Story or Use Case scenario within which the system event occurs, often named <UserStoryName>Handler, <UserStoryName>Coordinator, or <UserStoryName>Controller.
As an aside, this GRASP pattern is nicely enabled via ASP.NET MVC with an explicit controller layer. But as the naming of a controller would imply, the scope of each controller should be limited to that implied by its name, accordingly. For example, a controller by the name of "ManageUsersController" would imply performing User-related CRUD operations; User-related report generation should likely belong to a separate controller. Incidentally, this would also better support High Cohesion - what is good for the support of one pattern is often good for another.
It is sometimes difficult to figure out the very first step for adding behavior and collaboration activities to objects. A technique which may assist with getting the ball rolling or to help when brainstorming the behavior of a new bounded context within a domain is a technique known as CRC cards (Class-Responsibility-Collaborator cards).
CRC cards are very simple yet effective for defining brainstorming the responsibilities of each object. To utilize, grab some index cards, title each with a class name of interest, and write two things on each:
- Responsibilities: For each CRC card (i.e., each class), write one or a few bullet points describing the extent of responsibility for the class. Keep GRASP patterns in mind when defining the scope of responsibility.
- Collaborator Objects: List one or more collaborator classes which need to be interacted with in order to carry out the responsibilities of that class.
After documenting responsibilities and identifying collaborator objects, the task of transforming this information into the class diagram, and into sequence diagrams, is greatly simplified.
Refactoring & Refactoring to Patterns
GRASP patterns are a tremendous guide for answering the question of "what belongs where." But as a system evolves, changes are inevitably introduced by client request, or more frequently, by further discovery of requirements. For example, what may at first appear to be simple may quickly become complex after coding begins. In these cases, refactoring techniques can greatly assist to answer the question of "how to adapt to change." In a nutshell, refactoring is the practice of improving the design of existing code. While the details of refactoring are beyond the scope of this article, two tremendous resources (i.e., absolutely required reading for any developer) are Refactoring by Martin Fowler and Refactoring to Patterns by Joshua Kerievsky.
Putting it Into Practice
This day’s "Putting it Into Practice" will consist of a number of example artifacts, including the class diagram (both initial and revised), CRC cards, and sequence diagrams. Let’s review each in turn.
By far, transforming the concept model into the design model is the one of the trickiest tasks and the most important to get as close to "right" as possible. The rule to keep in mind is to design the simplest model which could possibly work to fit the needs of the requirements and elaborate iteratively and only when necessary. (Keeping in mind the design elaboration steps discussed previously.)
Initial Domain Class Diagram
At some point, the concept model must be transformed into an implementable design model. This is the time to put our techie hats on and start to think in terms of classes, properties and methods. To get the ball rolling, let’s take a stab at designing a minimal class diagram to support the concept model discussed earlier.
Initially, we’ll focus on the bounded context of support ticket management, and the objects which participate with this activity. We’ll leave the reporting context until later on; this will also give Acme Telecom a little more time to make up their mind on what they want to see in the report.
What follows is the class diagram reflecting the first attempt at modeling the domain as an implementable design:
There are a number of differences from the conceptual model which were carefully considered:
- The concepts Management, SupportStaff, and Admin have been combined into a single class called StaffMember. The following considerations were taken into account to make this decision:
- All of these concept objects are employees of Acme Telecom.
- Each of the concepts share duplicate data.
- While each may be allowed to do different tasks (e.g., manage staff vs. open support tickets), it does not yet appear that the classes themselves would behave differently. I.e., it’s not yet apparent that an Admin class would have different methods than a Manager class.
- Having the flags "IsAdmin" and "IsManager" may indicate that we may need role-management capabilities; while this may be true, we’ll want to avoid delving into the weeds further until warranted. (With that said, we’ll want to fully think out security management before we begin coding…it’s much more difficult to incorporate security management later on in project development.)
- The concepts SupportTicket and SupportCall have been merged into a single class called SupportTicket. This could have been argued either way, but the two concepts are equivalent from a data workflow perspective. Consequently, I went for the simpler approach.
- SupportTicket.Status has been pulled out as an enum value as we rarely expect the status types to change. Additionally, since the status may also be considered within data workflows, having it as an enum value will simplify the workflow conditionals and provide support for the use of a finite state machine, if deemed necessary down the road. (See Kerievsky's Refactoring to Patterns for guidance on when/how to make this decision.)
- Preferably, SupportTicket.IssueType would also be an enum to keep things simple. But, the requirements state that the SupportStaff must be able to dynamically add new issue types, when needed, when opening a support ticket. Consequently, IssueType will need to be a persistable domain class.
Take special note that this model does not imply anything about infrastructural details such as persistence and security. These will be added to the class diagram as they are included in the sequence diagrams. Trying to anticipate all of the infrastructural classes at this time will likely lead to over-engineering (or wrong-engineering altogether).
With that said, the architect should have a good idea of how such infrastructural concerns will be addressed in the final solution. It is appropriate to have at least discussed how the overall project will be architected and what tools will be used to support infrastructural concerns. The overall architecture will have a strong impact on the sequencing of messages through the application tiers.
For the task at hand, we’ll take into account the fact that we’ll be using S#arp Lite as our underlying framework and architecture.
Accordingly, before going further, read the article "S#arp Lite: The Basics" to get a better understanding of how a S#arp Lite project is architected: http://devlicio.us/blogs/billy_mccafferty/archive/2011/11/11/s-arp-lite-the-basicss.aspx . Pay particular attention to the package diagram, describing how the layers are organized.
Sequence Diagram for Opening a New Support Ticket
Did you read the introduction to S#arp Lite as referenced above? OK, great, we’re ready to continue. (You'll likely be lost, bewildered, confuddled and worse if you haven't read it...I hope you'll only experience one or two of those symptoms if you have read it.)
We have designed the beginnings of the class diagram. We’re now ready to elaborating, refining, and adding behavior to the class diagram by analyzing the sequence of events involved with opening a new support ticket. This particular user story was selected because it covers much of the bounded context that we’re currently working on and is a critical element of the CLogS application.
As we’ll see, the design of the sequence diagram is where we have to seriously start thinking about:
- How will data be loaded from the database?
- How will data be saved to the database?
- How will messages travel among the Web, Controllers, Tasks, Domain, and Data layers of the project?
- How will the Tasks layer expose the core API of the application to the Controllers layer?
While this is a daunting task, be patient and revise your first sequence diagram many times until you feel it’s clear and clean.
Let’s now consider a sequence diagram for the following user story:
Support Staff may open a new support ticket and provide details including: Customer, Issue Details (such as description and "dynamically managed" type), Status, and Resolution Details (if resolved immediately). Status of the call may be New, In Progress, or Resolved. ("Dynamically managed" means that if the issue type isn’t available, the Support Staff may enter a new one when entering the support ticket.)
To make things simpler, ignore any extraneous features in the first design of the sequence diagram to focus on the core of the requirements; e.g., we’ll ignore the dynamically managed issue types for now.
(Click to embiggen.)
The sequence diagram above describes the collaboration of objects in support of allowing a staff member to open a new support ticket.
While designing the above sequence diagram, four new objects were identified, which may be added to the class diagram:
- ManageSupportTicketController: This MVC controller class will reside in the .Web layer of the project and will have the responsibility of handling requests for the management of a support ticket.
- ManageSupportTicketTasks: This tasks class (aka "application service" class [Fowler 2002]) will reside in the .Tasks layer of the project and will have the responsibility of carrying out the coordination activities for the management of a support ticket.
- SupportTicketViewModel: This view model DTO (data-transfer object) will carry information from the controller to the view and vice-versa for the purposes of allowing a StaffMember to add/update support ticket information.
- IRepository<SupportTicket>: This interface will provide a means to communicate with the underlying data access mechanism to persist the new support ticket to the database.
In a nutshell, the sequence is as follows:
- The StaffMember requests to open a new support ticket.
- The StaffMember is presented with a form.
- A sub-process facilitates the StaffMember to select the respective customer (elaborated below).
- The StaffMember provides the requested information and submits the form.
- The StaffMember is either:
- presented with the same form, with validation information, if the submitted information contained validation problems, or
- presented with a listing of open support tickets with a message stating that the support ticket was successfully saved.
We’ll now turn our attention to look at the sub-process for selecting a customer...
The sequence diagram above describes the sequence of events to allow a StaffMember to select a customer via an AJAX post-back on the page. The form will have a "Customer Account Number" input box on it; when it changes, an AJAX post-back will try to find the customer record. If the customer exists, the customer information will be dynamically shown; if it doesn’t, the StaffMember will need to provide a few customer details before submitting the form.
As implied, many details about a user story are discovered during the process of designing a sequence diagram. To ease organization of the requirements, the sequence diagrams and details may be simply printed out and stapled to an index card with the user story written on it, or uploaded as attachments to the user story in an online requirements management tool.
In both sequence diagrams, arguments may be made that all of the UML is not "by the book"; e.g., the use of stereotypes has been basterdized a bit to make the diagram more expressive. I bring this up to express the point that less time should be spent worrying about making a UML diagram perfect as should be spent conveying useful information. When everything’s said and done, these diagrams will likely be thrown away once they’ve been implemented as working code.
If the underlying project architecture is already established (e.g., we’re using S#arp Lite as the foundation), designing the sequence diagram is relatively straight-forward. If the intention is to grow the architecture "organically," then elaborating the sequence diagram may take a few iterations.
CRC Cards for "Ticket Resolution Report by Support Staff"
In our discussions thus far, we haven’t gotten into the details of exactly how the support ticket resolution report will be generated. So before tackling the respective sequence diagram, we’ll use CRC cards to brainstorm a bit about the classes to be involved with the user story. The CRC cards don’t need to be exhaustively detailed; just enough in order to move on to the associated sequence diagram. CRC cards don’t/shouldn’t need to be maintained throughout the project life-cycle; once they’ve served their usefulness, toss them (or archive them away to some hidden place if throwing them away saddens you).
As demonstrated, the CRC cards were used to work out the basics of the classes involved with generating a support ticket resolution report, per the requirements. Now that the general responsibilities have been defined, we’re able to confidently move on to designing the respective sequence diagram.
Sequence Diagram for Creating a "Ticket Resolution Report by Support Staff"
(Click to invigorate.)
The sequence diagram above describes the collaboration of objects in support of the creation of a support ticket resolution report for a specific staff member.
While designing the above sequence diagram, four new objects were identified, which may be added to the class diagram:
- SupportTicketReportsController: This MVC controller class will reside in the .Web layer of the project and will have the responsibility of handling requests for the creation of support ticket related reports.
- SupportTicketReportTasks: This tasks class will reside in the .Tasks layer of the project and will have the responsibility of carrying out the coordination activities for the creation of support ticket related reports.
- IRepository<StaffMember>: This interface will provide a means to communicate with the underlying data access mechanism to retrieve staff member information.
- IRepository<SupportTicket>: This interface will provide a means to communicate with the underlying data access mechanism to retrieve support ticket information.
We’ll now turn our attention to look at the revised domain class diagram, taking into account what we’ve learned with the creation of the CRC cards and sequence diagrams.
Revised Domain Class Diagram
Now it’s starting to look like a real application! (On paper anyway. >:/ ) The class diagram now contains enough classes to warrant noting which project layer each class belongs in. We also have enough information to decorate classes with methods (i.e., behavior) in addition to attributes. If you’ve been paying attention, you may notice that some of the class names are slightly different from the CRC cards and/or sequence diagrams; e.g., ManageCustomersController was called ManageCustomerController in the sequence diagram. This is perfectly fine and expected; your design should continue to evolve as conventions are established, details are discovered, and new changes are incorporated.
With the sequence diagrams established to describe how the objects collaborate, and the class diagram designed to show how the classes are organized, we’re ready to begin switching gears from design to implementation. If you’ve been designing user stories along the way, the actual implementation should be the easy part!
- Domain class diagram
- Sequence diagrams
That wraps up Day 3 in this series. In Day 4, we'll begin the implementation phase of the project and get our hands dirty with some code!
10-16-2012 4:10 PM