|
Package and Deploy the Bank Application
In this part of the chapter, we'll use a Bank application as an example to demonstrate the design process starting from class diagrams to the completion of component and deployment diagrams. Although the design process is in several places tailored for distributed systems built on the .NET platform, it's general in essence and can be easily applied to systems built on other platform. Through the various steps in the process, we'll make the following design decisions:
What classes should be .NET remoting types and what their activation modes should be
How to identify the elements that should go into one component
How to share components
How to map components to physical deployment nodes
Unlike design-level diagrams such as use case diagrams, class diagrams and other types of UML diagrams, component diagrams and deployment diagrams are implementation-level. It is therefore very helpful having a basic understanding of how things are done technically. Unlike working at the design level, working at the implementation level can be platform specific. Here the platform of interest is of course .NET. We'll show in later sections the technical details of compiling and distributing .NET assemblies, and NET's mapping of UML components. System Requirements
For background information, we'll first make a brief system requirements list of the Bank application and give a partial class diagram of the system. Instead of formal use cases, we'll only briefly list out the parts of systems requirements pertinent to our discussion and demonstration. The Bank system we are going to build must fulfill the following requirements:
Bank customers can manage their accounts via web browsers. They can do things like checking account balance and transferring funds.
Bank staff can manage customer and account data. They can do things like creating or removing a customer or an account, setting an account's balance or credit line, get details of a customer or an account, etc.
Staff records are stored in a LDAP server. Administrators of the Bank application will add or remove a staff member, get details of a staff member, set a staff member's privileges, etc., through a central management console. Our application should read staff data from the LDAP server and allow a staff member to make changes to the business data according to their privilege settings. It is also known that our client's HR system uses the LDAP server for staff authentication and authorization.
The system must be able to conduct business with peer banks electronically using industry standards.
Our client has two data stores. One is for customer records. The other is for account records.
From the very primitive requirements list, we'll take a leap of faith and say that the system comprises the following types: AccountController, Account, CheckingAccount, SavingsAccount, CDAccount, Tx, TxLog, and others. Below is a class diagram showing part of the system's classes.
We'll use the types listed as the starting point of our discussion. Based on the types, our goal is to come up with a design for packaging and deploying our distributed Bank application. In other words, our goal is to start from the class diagram and end with component and deployment diagrams. .NET Remoting Type and Activation ModeAt this point of the design process, we would like to examine the class diagram and identify the classes that should be made .NET remoting types. If a type in the class diagram will be invoked remotely by some code in a different process, then the type has to be a .NET remoting type. In other words, it has to inherit System.Runtime.Remoting.MarshalByRefObject either directly or indirectly. Deciding whether a type should be a .NET remoting type is straightforward. In our Bank example, we have the following account-related types: AccountController, Account, CheckingAccount, SavingsAccount, CDAccount. The job of AccountController is to control/manage bank accounts. It has methods for creating accounts, removing accounts, adding customers to an account, removing customers from an account, etc. The Account class and its derived types are to represent the various kinds of bank accounts. Which one of them should be accessible to programs running on remote machines? None. The account-related types should be used internally in our application. We don't want clients of our application to access those types directly. We don't want clients to gain unnecessary insights into our system, so that we have the flexibility to change the internals of our system without breaking client programs. Clients of our application don't want to access those types directly either. They are not interested in such detailed knowledge of our system, and would like to interact with an interface that can hide the systems details. To that end, we add a new type, AccountFacade, to our system. The purpose of adding AccountFacade to our system is to consolidate and simplify the account-related interface our system exposes to clients. By adding AccountFacade to our system, we also eliminate the need to make the other account-related types .NET remoting types. AccountFacade will be a .NET remoting type itself. All account-related requests from remote clients will go through AccountFacade and AccountFacade will work together with the internal account types to fulfill those requests.
Once you decide that a type should be a .NET remoting type, the next decision to make for the type is its activation mode. Deciding the activation mode of a remote type is based mainly on whether the type is stateless. Remember that in the .NET Remoting primer section, we mentioned that there are three possible activation modes for remote objects: Singleton, SingleCall, and ClientActivated. If the type is stateless, then it should be SingleCall. If the type is stateful and has only class level states, then it should be Singleton. If the type is stateful and has only instance-level states, then it should be ClientActivated. The activation mode of a remote type should be decided as we prepare the class diagram. In our Bank example, we know that AccountFacade, CustomerFacade and TxFacade are .NET remoting types. Although we didn't show it in a class diagram, those types are all stateful. They have member variables to maintain their states. Therefore, they cannot be SingleCall. They cannot be Singleton either because the states they maintain are instance-level states. Identify the Elements that Should Go into One Component.A component is a collection of elements that form a cohesive and logical unit of functionality. The question is: how do we identify the elements that should go into one component and how do we compose the component from those elements? Identifying the elements that should go into one component is a harder decision to make than the previous ones. The basic rules for grouping are:
Functionality: package elements that work together and that form a logical unit of functionality into a component. This is the essence of assemblies.
Deployment: Package elements that form a fundamental unit of deployment.
Reuse: Package elements for reuse. Elements that work together to provide a cohesive set of functionalities should be packaged for reuse.
Security: Packaging for security is sometimes necessary in not only distributed systems but also local systems. However, for distributed systems, security is an area that needs particular attention. For example, a local system might have some parts that run under the system account and other parts that run under a less privileged user account. A distributed system might have some parts such as web components that run outside the protection of firewalls and some parts such as business components that run under the protection of firewalls.
Performance: Many performance boosters such as load balancing and object pooling are implemented in system-level infrastructure. For application-level designers, the main focus for improving system performance should be on packaging components so that network traffic flowing among components can be minimized.
When designing a system, one can take a bottom-up or a top-down approach. Using a bottom-up approach, the designer recognizes types from use cases first and then groups those types into larger piece components. Using a top-down approach, the designer recognizes the larger pieces first and then breaks down each piece into smaller parts. The designer can verify their top-down design by checking if the design covers all types that one can derive from use cases via a bottom-up approach. From the naming of the types listed above, you can see that we took a mix of both the top-down and bottom-up approaches when designing the system. When we were designing the system, we had an idea that the system would consist of components such as account, customer, and transaction. Therefore, we gave the types in each potential component descriptive names. Here we'll start from the packaging rules listed above and verify if our grouping of the account-related classes into the Account component is adequate. Adding a Detailed Component Diagram
After identifying components of the Bank system and the elements each component contains, we are now ready to use Visio to model that information and prepare component diagrams of the Bank system. We'll add to the Implementation Model folder theUML shapes and diagrams that belong to the implementation model phase of the system design process. Therefore, we'll add a component diagram and several component shapes to the Implementation Model folder. The component diagram will give a detailed view of the components the Account subsystem of the Bank.
Remember that in the .NET Remoting section we said that remote objects (instances of .NET remote types) need a host process to house them. In our Account component, we have a .NET remote type, the AccountFacade class, and we need a host program for it. Assume that the host program is the Host.cs file listed in the .NET Remoting section. We use the following command to compile Host.cs and generate Host.exe.
Also, remember that in the .NET Remoting section we said that an EXE host program usually reads runtime configurations of remoting objects from a configuration file. A sample configuration file was shown in that section. Putting all those pieces together, the Account assembly, the host program, the configuration file, and the underlying .NET remoting infrastructure, we can draw up the component below:
The component diagram above shows four components: the Account assembly, the host server, the run-time configuration file, and the .NET remoting infrastructure. We'll now walk through the steps of creating the above diagram in Visio.
Add a Component diagram
First, we'll add a component diagram to the Implementation Model under the Top Package and name it AccountComponent. Right-click on the Top Package subfolder of the Implementation Model folder and select New | Component Diagram in the pop-up menu. Rename the newly created component to AccountComponent by right-clicking on the component, selecting Rename in the pop-up menu and then typing in AccountComponent.
Add Component shapes
In the AccountComponent diagram, we'll add four component shapes for each of the four components shown in the above diagram. To add a component shape to the drawing space of a diagram, locate the Component shape in the UML Component stencil, drag it and drop it onto the drawing area. To add the Account component to the AccountComponent diagram, drag and drop the Component shape and rename it Account.
Notice that as you add the Account Component shape in the drawing area, an element appears in the Model Explorer under the Top Package folder. This is handy because from the Model Explorer, you can quickly see the elements in a package and the packages in a model. What's more, the elements shown in the Model Explorer and their dependencies can be reused in other UML artifacts as we'll see later in the chapter when we prepare Deployment diagrams. Repeat the steps for adding the Account component and add the other three Component shapes to the AccountComponent diagram. You should see the three corresponding elements under the Top Package folder.
Add Interface shapes
After the Component shapes are in place, the next thing we'll do is model the public interfaces of components in our Component diagram. We do so by attaching interface lollipop shapes to Component shapes so that users of our diagram can tell the public interfaces of a component. We'll attach an interface lollipop shape to the Account component shape and name it IAccountFacade. By doing so, we model into the AccountComponent diagram the fact that the Account component exposes to the public the IAccountFacade interface. To attach the IAccountFacade interface lollipop shape to the Account component shape, locate the Interface lollipop shape in the UML Component stencil, drag and drop it to the drawing area. Attach the Interface lollipop shape to the Account component shape. Rename the Interface lollipop shape to IAccountFacade. Repeat the same steps for adding the RemotingConfiguration interface to the .NET Remoting service component. Notice that like Component shapes, the Interface lollipop shapes we just added to the drawing appeared in the Model Explorer.
Add Dependency shapes
Besides components and interfaces, we also need to model dependencies among components in the AccountComponent diagram. This is done by dragging the Dependency shapes in the UML Component stencil and dropping them in the drawing area. In the AccountComponent diagram, we modeled three dependencies, one between the Account and the Host server component, one between Run-time configuration and Host server, and the other between Host server and .NET Remoting service. Notice that Dependency shapes are not elements under the Top Package folder in the Model Explorer.
After completing the above steps for preparing the AccountComponent diagram, the Model Explorer should look like the one shown overleaf.
Adding a Bird's-Eye-View Component DiagramA component diagram can show the components that make up a system in great detail. It can also give a bird's eye view of components that make up a system and their dependencies. The diagram below is also a component diagram of our Bank application. However, unlike the diagram in the previous section, it shows only the larger pieces. If you break down the Account component in the diagram below, you get the more detailed diagram like the one shown in the previous section.
The component diagram above shows that our Bank application consists of seven large components. The four components at the top are for conducting B2B electronic banking with business partners. The four components at the bottom are for customers to do online banking and for staff to manage accounts and customers. You can follow the procedures and steps we used in the previous section to draw the above diagram. The name of the Component diagram we created in this section is BankComponent. Notice that when drawing the BankComponent diagram, we should not drag and drop a Component shape from the UML Component stencil for the Account component. The Component shape for the Account component was drawn in the previous section when we prepared the AccountComponent diagram and it appears in the Model Explorer as an element. To draw the Account component in the BankComponent diagram, we should drag and drop the Account element in the Model Explorer instead. After completing the drawing, the Model Explorer should look like the one shown below.
Component PackagingIn the previous sections, we prepared Component diagrams of the Bank application. The designing and drawing of Component diagrams is platform independent. Exactly how code pieces belonging to the same component are compiled and grouped together depends on the platform you build your application upon. Here, we'll look at the technical, implementation-level details of how to compile types into modules and modules into assemblies for packaging related, cohesive types into components on the .NET platform. First, we want to ask what the mapping of a UML component is in .NET. Let's refresh our notion of a UML component a little. A UML component is, as described in the UML Specification (version 1.4, section 2.5.2.12), "a modular, deployable, and replaceable part of a system that encapsulates implementation and exposes a set of interfaces." The description is very general and gives us only the traits a component should possess. From that description, a UML component can be a broad range of things. Typically, a UML component will be an executable (EXE), a dynamic-link library (DLL), and a bunch of source code files or binary code files. In .NET, there is a specific name for UML components. We call them assemblies. A .NET assembly is, as described in the .NET documentation, "a collection of types and resources that are built to work together and form a logical unit of functionality." Furthermore, the .NET documentation says an assembly is "a fundamental unit of deployment, version control, reuse, activation scoping, and security permissions." It is apparent that this description of an assembly matches the traits of an UML component. If you have an assembly to package and share, you have to consider the following two questions:
How do you want to share the assembly? That is, do you want to share the assembly privately or publicly?
What do you need to do to share the assembly the way you want?
In .NET, an assembly can be shared with other assemblies locally or globally. Sharing an assembly locally means only a certain set of assemblies can locate and access the shared assembly. Sharing an assembly globally means all assemblies can locate and access the shared assembly. You have to decide how you want to share an assembly, locally or globally. Usually, when an assembly is intended to be used by not only your application but also unknown applications developed by others, the assembly should be shared globally. An example for this type of assemblies is a third-party .NET component that performs functions like spell checking or file uploading. Components that perform spell checking or file uploading are so general that they could be utilized by, and therefore should be shared globally with, all applications that need access to them. If you want to share an assembly locally with a certain set of assemblies, you have to put a copy of the shared assembly's DLL either in each of the folders of the dependent assemblies or in the private search paths of the dependent assemblies. On the other hand, if you want to share an assembly globally with assemblies in any application or if the assembly will be used in COM+ components, you have to put the shared assembly's DLL in the global assembly cache (GAC). Let's use the Account component as an example. The component has the following classes: Tx, TxLog, Account, CDAccount, CheckingAccount, SavingsAccount, AccountController, and AccountFacade. Each of them is in its own C# source file. Our naming convention for the C# source files is the class name followed by the file extension (.cs) for C# source files. For example, the definition of Account is in Account.cs. Grouping all the classes into one component means compiling all the source files into one assembly. To compile the source files into one assembly, we first compile each of the source files into a module. Then we generate the assembly from the modules. The command below compiles Account.cs into a module. The result module contains the MSIL code and metadata for the Account class. The module filename is Account.netmodule.
The following four commands compile CDAccount, CheckingAccount, SavingsAccount, and AccountController into their own modules respectively. Because the four classes reference the Account class in Account. netmodule, we have to indicate that reference in the commands using /addmodule.
The command below compiles TxLog.cs into a module. The module filename is TxLog. netmodule.
The command below compiles Tx.cs into a module. Because the class Tx references the Account class in Account.netmodule, we have to indicate that reference in the command using /addmodule.
The following command compiles AccountFacade.cs and all the previously created modules into a DLL named AccountFacade.dll. The AccountFacade.dll and all module files together are our Account component. In other words, the Account component in our Bank application is a multi-file assembly that consists of AccountFacade.dll and the module files. The DLL file contains not only the MSIL code and metadata for the AccountFacade class but also the assembly's manifest.
This figure above illustrates the relationships among modules, assemblies, manifest, and metadata. It is important to note that the purpose of this section is to show you how to technically package classes into an assembly in the .NET platform so that you have a more solid feeling of the correlation between conceptual design and physical implementation. However, just because you know how things are done physically doesn't mean that you should put everything you know in a design document. UML is platform independent and not tied to the build process of a specific platform. How the components of your application are built is irrelevant to UML. Therefore, don't model the build information in an UML component diagram. Map Components to Physical Deployment NodesThe Component diagram shows the various modules of our application but it does not tell you which module will go on which hardware platform. The job of a Deployment diagram is to fulfill that need and provide designers with a standard way of documenting system deployment. Thanks to Visio and its UML Model Diagram Template, creating a deployment diagram is a breeze once you have the component diagram ready. If you know how to leverage the help Visio provides, you'll find that the time and hard work you invested in drawing a component diagram is rewarding when you are trying to prepare the deployment diagram. Let's open up the component diagram we made previously. If you don't see the Model Explorer window, then open the UML menu, point to View and click on Model Explorer to bring up the Model Explorer window. In that window, you should see the Implementation model we created previously in the tree hierarchy. We'll now add a Deployment diagram to the model under the Top Package subfolder of the Implementation Model folder and name it BankDeployment. In the drawing space of the newly created BankDeployment diagram, we can now drag and drop elements from the UML Deployment stencil as well as elements in the Top Package. This saves us a lot of headache because in BankDeployment, we can reuse elements that we added to the model when preparing the BankComponent diagram. During the requirements analysis phase, we knew from interviews with our client that they have two database servers, one for storing account records and the other for storing customer records. For performance reasons, we decide to deploy the Account component on a machine close to the database server that stores account records. Similarly, we'll deploy the customer component on a machine close to the database server that stores customer records. So we'll have four nodes in our deployment diagram, one for the Account component hosted on one app server, one for the Customer component hosted on another app server, and two nodes for the two database servers. This is shown in the diagram opposite. Notice that we made the decision of deploying the Staff component on the same machine as the Customer component. Besides the four nodes, we also see some others. The Staff component accesses an LDAP server shared by our Bank application, client's HR system, and a central management console. Here in the deployment diagram, we only show the LDAP server because our Staff component connects to it directly. The HR system and central management console are not relevant to our system deployment and they shouldn't be modeled in the diagram. In the diagram below, a node runs the web server hosting web pages for B2C electronic banking. On the side of B2B electronic banking, we have an app server for hosting the three B2B components.
The beauty of using Visio to prepare a diagram like the one shown above is that you only need to drag node shapes from the UML Deployment stencil and component elements from the Top Package, place them where you would like them to be in the drawing area, and name them properly. The dependencies among components are automatically carried over from the BankComponent diagram. We'll now walk through the steps of creating the above diagram in Visio.
Add a Deployment diagram
First, we'll add a Deployment diagram to the Implementation Model under the Top Package and name it BankDeployment. Right-click on the Top Package subfolder of the Implementation Model folder and select New | Deployment Diagram in the pop-up menu. Rename the newly created diagram to BankDeployment by right-clicking on the node representing the diagram in the Model Explorer, selecting Rename in the pop-up menu, and then typing in BankDeployment as the new name for the diagram.
Add Node shapes
In the BankDeployment diagram, we'll add Node shapes for each of the eight nodes shown in the above diagram. To add a Node shape to the drawing space of a diagram, locate the Node shape in the UML Deployment stencil, drag it, and drop it on the drawing area. To add the Web server node to the BankDeployment diagram, drag and drop the Node shape to the drawing area and rename it to Web Server.
Notice that as you add the Web Server Node shape in the drawing area, an element appears in the Model Explorer under the Top Package folder. This is the same as what we've seen in previous sections where we added Component shapes or Interface shapes to a Component diagram. Repeat the steps for adding the Web Server node and add the other Node shapes to the BankDeployment diagram.
Add Component shapes
After the Node shapes are in place, the next thing we'll do is model the information of what component of the Bank system resides on which node. We do so by dragging and dropping the Component elements in the Top Package folder. This reuse of Component elements we created in an earlier Component diagram is a time-saving feature of Visio. It also reduces the chance of having inconsistencies between Component diagrams and Deployment diagrams. As you place the Component elements in the drawing area of the BankDeployment diagram, you'll notice that the dependencies among components are carried over from the BankComponent diagram.
Add additional Dependency shapes
The Dependency shapes that are carried over from the BankComponent diagram are dependencies among components. Besides those dependencies, we might need some others for denoting the dependencies between two nodes or between a node and a component. To model those extra dependencies in the BankDeployment diagram, we'll drag the Dependency shapes in the UML Deployment stencil and drop them in the drawing area. In the BankDeployment diagram, we have a dependency between the Staff component and the LDAP Server node, a dependency between the App Server - Customer node and the DB Server - Customer Data node. We also have a dependency between the App Server - Account node and the DB Server - Account Data node, and a dependency between the Firewall node and the App Server - B2B Electronic Banking node.
After completing the above steps for preparing the BankDeployment diagram, the Model Explorer should look like the one shown opposite. We can see that under the Top Package, we have a few new elements. They are the various nodes in the BankDeployment diagram.
We would like to make a point before ending this section. Showing how components are distributed is the purpose of a deployment diagram. When we say how components are distributed, we don't mean the physical means you use to install programs on production machines. What we mean is the logical topological placement of components. The physical means that you use to distribute components of your application is irrelevant to UML design. One can choose to package assemblies into .cab or .msi files and let users install the application by using tools such as InstallShield or Microsoft Installer. So, don't model this information in an UML deployment diagram.
|
No comments:
Post a Comment