Well, that concludes our diversion into application architecture. It might have seemed a little tough-going, but it is important for all team members to understand the basic issues here. This information will have a major impact on how we design our components. So, now we've got all that under our belt, we can move on to see how to design our object interfaces from the use cases.
Remember that we looked earlier in the chapter at the use case for entering an order and identified three objects: Product, Order and Customer. This use case included the line:
> Actor will Create Customer if the customer does not exist
This operation contains a series of steps of its own, and can be expanded into another use case, which will specify exactly what these steps are:
> Primary Actor: Order Entry Clerk
> Actor requests to add new customer
> Actor enters customer name
> Actor enters customer address
> Actor enters customer phone number
> Actor enters customer fax number
> Actor enter customer credit card number and expiration date
> Actor saves customer information
We are going to take this use case to create a sequence diagram mapping out the sequence of events that are required to fulfill this goal. Before we even begin, you will notice that we are missing two important objects. Our use case says "Actor enters", which implies that the actor has some way to enter information into the system. In a Visual Basic application, the way for human actors to enter information will be through a user interface. We will therefore, need an additional object to perform services for the user, the User Interface, which will allow information to pass from the user to the system and from the system to the user. All of the client services will be performed by the user interface.
The other object we are missing is the object that will actually perform the updates to the database: our data services object. When building a three-tier project, the middle tier acts as a middleman between the client component and the database. The middle tier will receive requests to retrieve information from or update information to the database from the client component. The client component will gather the information and then send the request to the middle tier. The middle tier will then communicate to the database and perform the request. We therefore need a middle-tier object that will perform these services.
The middle-tier component and the user interface are two objects that are implied in the use case. Remember, the users are more concerned with the goals they want the system to accomplish for them and the services the system must perform than the actual components of the system. The developers must review the use cases, begin to draw out sequence diagrams and find the implied objects.
While we can see that the data services were separated out so that they can be placed into an MTS component for maximum efficiency, scalability and sharing of these services, you may be wondering why we separate out the business services and the client services. The client services will be very specific to the application and will generally not be very reusable. Thus, if I make an order entry application from a Visual Basic executable and remake a similar application for customers to place orders over the Internet, it is unlikely that much of my executable interface code will be reusable for my Internet application. On the other hand, the business objects that I create for my executable, such as the Customers, Orders, Order Details and Products objects, will all be easily reused in my internet application. By creating separate components that perform business services and client services, we have actually separated out the reusable components (the components that perform business services) from those that are probably not reusable (client service components, i.e. user interfaces).
The ability to upgrade the application is another reason we separate the business services and the client services. It is likely that our application will also have to be upgraded at some point to fulfill new business requirements that have resulted from the changes in technology and the market. It is likely that most of these changes will not affect the external methods and properties of the business components, but instead affect the internal rules of these objects. Thus, a change in tax laws may alter the way the order object internally calculates the sales tax, but externally there will still be the same Save Order method that will trigger the sales tax calculation. Thus, for the most part, when we have separate business objects we can upgrade our applications by creating internal changes to these objects that will not have any affect on the rest of the system.
When we actually change the way business is done and the interface of the business object must be changed, we can make these changes and plug these business objects into a new interface. The vast majority of the code will be reusable. Usually, only one or two objects will have to change resulting in the change of only a few interfaces. If we needed to make this change in an application where there was no separation of the business and client-service components, we would probably end up rewriting the entire application. Thus, the separation of business and client services allows us to reuse business components and easily upgrade the application.
Because the primary purpose of creating separate components that contain the business logic is for the ability to reuse and upgrade these components, their location in the physical system is usually not that important. Thus, we could place the business logic on the client computer or on a physical server. As these components do not make any direct connections to the database and they will probably maintain state, there is no requirement to place them on a server or to make them MTS components. The factors that will influence the location of the business components will usually be how often they will need to be upgraded (a continually changing component would probably be best on a server since it will only need to be replaced on one machine, rather than on every client), how easy it is to deploy the components to the client and the type of application being built.
The use case has provided us with detailed information on the interaction between the user and the client services object (the user interface). When we look at our use case, we see that it specifies that the actor will request a new Customer from the system. The actor will then enter the customer name, address, phone number, fax number and card number and expiration date. Finally, the actor will save customer information. Thus, the use case has given us all of the information for the communication between the user and the client-services component. Each of these operations represents a 'message' sent from the actor to the user interface, and will be represented in the sequence diagram by a line from the user to the interface.
When we reviewed the nouns in our use case, we saw that we needed a Customers business services component. We can see from the use case that this component will need to perform two tasks: creating a new customer (AddNew) and saving the customer information (Save).
The services that will be performed by the data services do not directly come from the use case. The developers will review the services that the business services components will need to perform, and based on these services they will decide what services the data services component will have to perform. In this case, the data services component will save a new customer and indicate whether the save was successful.
Our sequence diagram for creating a customer will thus look as follows:
There is really an incredible amount of information in this diagram. We can now see that we will need some additional methods in our customer component. Each arrow represents a request for some object in the system to perform a service. As we said, most of these requests will center on getting information to set properties or will be the actual setting of the properties. Luckily, Visual Basic will allow us to make user interfaces that do not require us to write complicated code to allow the user to enter information into the user information. Most of the requests to the user interface for services will actually be fulfilled simply by creating a text box for the actor to type the information in (or a list or combo box to select the correct information). Other requests, such as 'Request to Create a New Order' will actually require the customer object to have a special object to perform the service (in this case Add New): an object cannot create itself out of nothing, so we need to have an object which has already been created to which we can make the request for a new Customers object. We will see how to do this in the next chapter.
Services performed by the user interface will usually involve adding text boxes or list or combo boxes. The services performed by components will involve setting properties of the object and/or calling methods. Our sequence diagram allows us to see exactly what objects we need, what methods and properties these objects will need, which objects request services from other objects, what information must be supplied to perform the service and what information has to be returned when the service is complete, and what information must be inputted by the user and returned to the user by the system. So there is really a great deal of information in our sequence diagram.
From our 'Add New Customer' diagram, we can see the following:
> The customers component will require an AddNew method (request to create new customer).
> The AddNew method will not require any information passed in (no parameters) but it must return a new Customers object (thus it must be a function and have a Customer object as its return value).
> The user interface will need a text box or similar control to enter customer name, address, phone and fax numbers, and credit card number and expiration date.
> While it is not explicitly stated, the information placed into the user interface will need to be passed to the customer object before the save occurs. Thus, our Customers object will need properties for customer name, address, phone and fax numbers, and credit card number and expiration date.
> The Customers object will need to have a method to save a new customer record. This request to save the information will be passed onto the middle-tier component. This Save method will take one parameter, a Customers object.
> The middle-tier component will need a method actually to save the information to the database. This Save method will also take a Customers object as a parameter. The database was not listed in this diagram as a separate component, but we could have also included it, as the database is an object of the system. This was not included to keep the diagram simple, but we will show it in a more detailed diagram below.
> The physical location of these components (what tier they will be placed on) could also be placed on the diagram.
When we add the database component to the diagram, we can see that the application is spread across three tiers. The refined sequence diagram looks as follows:
This diagram now tells us that there is a database component. The request to save a customer will be passed from the user interface to the client customer component, to the middle tier component and finally to a stored procedure in the database. We can also see where the components will be placed in our three-tier application. The developer has added numerous components and messages that the user is unaware of, such as the database, middle-tier and user-interface components, and the methods associated with these components. This is the normal process of making a sequence diagram: we begin with the information in the use case and then expand the information to include implied objects and the methods and properties of these implied objects. The sequence diagram may go through many revisions until it reaches its final version. Some of the revisions may force a rethinking of a use case and force it to be redesigned. This is the nature of iterative design.
From these sequence diagrams, we can begin to define our component's public interface. In fact, we have already identified most of the methods and properties we need. As an example, let's look quickly at the interface for the Customers component. We have already seen that our Customers component will need the following methods:
> AddNew, to create a new customer
> Save, to save the information about a customer
> Edit, to update the customer information
We will also need a way of retrieving a specific instance of our Customers object. To do this, we will add methods for moving through the collection of existing customers and retrieving a given Customers object:
> MoveFirst, to retrieve the first customer
> MovePrevious, to retrieve the last customer
> MoveNext, to retrieve the next customer
> MoveLast, to retrieve the last customer
The Customers object will also need properties that contain information about each of our customers. We can see from the use case and sequence diagram that the information we need to store requires the following properties:
Sequence diagrams can be built using sophisticated tools such as the UML tools built by Riverton (HOW), Rational (Rational Rose or Visual Modeler) or Visio. You can also hand-draw them on pieces of paper to see the flow of your system, or use graphic programs such as SmartDraw. Use whatever works best for you and the situation you are in. If you are in a meeting with a client and need to show quickly how the components of the system may interact, a sequence diagram quickly hand-drawn can provide an easy way to view the interaction of the different components. If you are preparing documentation for your design document, you should probably use a professional tool.
The advantage of using UML is that there is no longer any guesswork involved. We still have not identified how the methods are going to work - we have not designed a detailed map of what the Visual Basic code will look like in each method, but we will do that in the physical stage. However, we have identified all of our objects and the methods and properties that will belong to these objects. We have also identified where our components will be located, how they will communicate to other objects in the system and how they will communicate to the user.
Before one line of code is actually written we know exactly what the system will do and have a very clear set of documents (the use cases and sequence diagrams) that can be shown to the user. The user can review these documents to make sure that all of the goals that the user needs the system to accomplish can be fulfilled by the system and that the system fulfills these goals in the manner that the user finds most efficient. As the more detailed diagrams may be confusing to the user, it may be best to show them diagrams that only show the interaction between the user and the user services object. When an agreement has been reached on the following items, the developers and users can sign off on the design documents:
> A list of the objects that will be required to fulfill the services of the system.
> The public methods and properties of these objects.
> How the objects will communicate to each other and to the users.
The user will know exactly what the system will do, and the developers will know exactly what they have to build. The testers will know what modules will need to be tested so that they can begin a detailed set of test plans. The educators will know exactly how the system will function so they can begin putting together user documentation and planning training for the new system.
Without use cases and sequence diagrams, there often is nothing more than a list of things the user would like the system to do. From this list, objects are identified and methods and properties for these objects are created. The developer will begin to start coding these objects, putting in the necessary methods and properties. They will then begin to build a user interface based on the developer's concept of what is needed. Once the interface is coded, the developer will begin to write code to pass information into the object. This process may result in the discovery of new methods and properties that the object will need, and these will be added. As the object is coded, the developer will discover that he needs to pass information to the middle-tier component, so methods will be added to the component. This style of coding is much like a man walking into a labyrinth in the dark with a flashlight that only produces a light beam that shines two inches out. In this situation, the person takes a step, examines the next two inches, and depending on what he sees in front of him, makes a decision where his next step will be. It is quite possible that three inches to his right, just outside of the range of the flashlight, is the exit to the labyrinth. But he will not see it unless he is lucky enough to take a turn to the right. He can only make decisions based on what the next immediate step is. He cannot look at the next fifteen steps and see how these fifteen steps may affect his next step.
However, with a detailed set of use cases and sequence diagrams, we can map out the entire system from beginning to end. We can see how changing one component of the system will affect the entire project. In our final sequence diagram, we included a final step that involved saving the customer to the database using a stored procedure. It is also possible that we could use SQL code in our middle-tier component to save the information directly to the database. Which is better? Well, what if we already had a set of stored procedures in our database to save customers? If we did not use stored procedures, we would have to write extra code in our middle-tier component. This could add one day of development time for the component. Adding this functionality into all of the Update methods in the middle-tier component may add several weeks to coding this project. Even if we had the time and financial resources to do this, is there any advantage to be gained by not using the stored procedures? That would allow us to do batch updates with disconnected recordsets in the middle tier and to check for records that failed to update due to inconsistencies. These inconsistent records can then be made consistent by the middle-tier component. Thus, by not using the stored procedures and placing the update functionality in the middle-tier component, we can move to a more flexible three-tier architecture. While this was a fairly simple example, we can investigate completely different scenarios by simply redrawing the sequence diagram.
One thing you must be careful about is not stopping once you find a solution to your problem. Once an initial set of use cases and sequence diagrams have been drawn up, the developers should come together for a brainstorming session. They should review the diagrams and try to find alternative ways of accomplishing the goals of the system. Alternative use cases and sequence diagrams should be created. Once several possible solutions have been arrived at, if this is possible (some systems only have one workable solution), the best solution should be chosen.
Often, the best solutions are never found because the people looking for a solution stopped at the first workable solution they came to.
There is another advantage to using sequence diagrams: they show the details of the services performed by the user, and we can use these to make sure we have a well-designed graphical user interface (GUI). The user interface is the most important part of the system to the user. We can create an application that solves all of the problems of the business, but has a lousy user interface so no one will ever use it. Thus, once the sequence diagrams have been completed, we should go into a detailed design of the user interface. We shall see how this is done in the next section.
Was this article helpful?