We have discussed 42 design patterns in earlier chapters in different sections of this book. Now it is time for us to see how some of these design patterns can be applied together in building a software solution. The objective of this case study is to identify and apply some of the design patterns discussed in this book in developing a software solution for the business requirements of a fictitious Web hosting company, KPS Hosting Solutions.
Figure 45.1 Customer Association with Other Business Objects
Figure 45.2 Reseller Association with Other Business Objects
As can be seen from the business requirements section, the application functionality consists of a set of services — customer management, search management, credit card services, address validation, employee management, etc. The overall application functionality may be modularized at two levels.
The first level of modularization can be accomplished at the service level. In other words, each of the services can be designed as an individual module. To provide client objects with a uniform interface to access these services, each service can be designed either as an implementer of an interface or as a subclass of an abstract or concrete class that declares the interface to be used by client objects. Figure 45.5 shows the design of different service modules as implementers of a common EnterpriseService interface.
Figure 45.3 Hosting Plan Class Hierarchy
Figure 45.4 Website–TroubleTicket-Employee Association
An enterprise service offers a group of related lower-level services to its clients. In other words, an enterprise service allows the processing of a set of related tasks. The terms “lower-level service” and “task” are used synonymously in this discussion. Each of the EnterpriseService implementers expects to receive a client request in the form of an XML request. One of the advantages of sending the request as an XML string is that it does not bind the client to a particular method signature on the service module. Another advantage is that it provides language independence. That means, clients and service implementers can be implemented in different programming languages and thus allow for the integration of legacy applications written in Fortran, COBOL, C++ and other programming languages.
Figure 45.5 Service Module Class Hierarchy with Each Module as an Implementor of a Common Interface
A service implementer is responsible for defining the required interface contract details to be used by different clients. This involves defining the request and the response structures for the service.
<ENT_SERVICE_NAME>
<Task name='specificService'>
<Input_1>abc</Input_1>
<Input_2>xyz</Input_2>
…
…
<Input_n>something else</Input_n>
</Task>
</ENT_SERVICE_NAME>
Response:
<ENT_SERVICE_NAME ErrorMsg=’N’>
<RESPONSE>
<Output_1>abc</Output _1>
<Output _2>xyz</Output _2>
…
…
<Output _n>something else</Output _n>
</RESPONSE>
</ENT_SERVICE_NAME>
or
<ENT_SERVICE_NAME ErrorMsg=’Y’>
<ERRORS>
<ERROR>
<CODE>01</CODE>
<MESSAGE> Error Message 1</MESSAGE>
</ERROR>
…
…
…
<ERROR>
<CODE>0n</CODE>
<MESSAGE> Error Message n</MESSAGE>
</ERROR>
</ERRORS>
</ENT_SERVICE_NAME>
Where:
<ENT_SERVICE_NAME> is the name of the enterprise service.
<Task> is the lower level service offered by the service.
Request:
<CREDIT_CARD_SERVICE>
<Task name=‘validateCard’>
<CardNumber>1234123412341234</CardNumber>
<CardType>VISA</CardType>
<ExpDate>01-12-2008</ExpDate>
<CardHolderName>CardHolder</CardHolderName>
</Task>
</CREDIT_CARD_SERVICE>
Response:
<CREDIT_CARD_SERVICE ErrorMsg=’N’>
<RESPONSE>
<CardNumber>************1234</CardNumber>
<Status>Valid</Status>
</RESPONSE>
</CREDIT_CARD_SERVICE>
or
<CREDIT_CARD_SERVICE ErrorMsg=’Y’>
<ERRORS>
<ERROR>
<CODE>05</CODE>
<MESSAGE>Unable to Connect to the Provider For
Verification</MESSAGE>
</ERROR>
</ERRORS>
</CREDIT_CARD_SERVICE>
Whenever a client needs to access the services of an EnterpriseService implementer, it needs to:
As mentioned earlier, an enterprise service component offers a set of related lower-level services (tasks). If the enterprise service module itself is made respon-sible for implementing these lower-level services, it could lead to a design that is very restrictive and hard to maintain. Whenever a new task needs to be added to the enterprise service or a task needs to be removed, it requires changes to the enterprise service module. To avoid these problems, an enterprise service module can be designed to make use of a set of predefined objects to handle the set of tasks that it is designed to process. The mapping between a lower-level task and its processor or handler can be specified in the form of an XML file.
<TaskMappings service=’ENT_SERVICE_NAME‘>
<Task name=’Task_Name’>
<handler>package.class</handler>
</Task>
</TaskMappings>
<TaskMappings service=’CREDIT_CARD_SERVICE‘>
<Task name=’validateCard’>
<handler>com.company.entservices.ccservice.CardValidator
</handler>
</Task>
</TaskMappings>
Whenever an enterprise service component is initialized, it needs to read the corresponding Task-Handler mapping into memory. Maintaining a separate Task- Mapping XML file for each enterprise service works better in an environment where individual teams are responsible for developing specific enterprise services modules. When a client request is received, the service component can check the mapping list to find the handler. Once the handler is found, the service component instantiates the handler class and submits the request XML file to it. To make it possible for all enterprise service components to treat all handlers in the same manner, every handler must offer the same interface for an enterprise service module to forward a client request. Towards this end, every processor class can be designed as an implementer of a common Interface (Figure 45.6).
A handler can in turn make use of other helper classes in accomplishing the task it is designed for. The mapping between a task and a handler is one-to-many. In other words, a handler may process more than one task. For every task that a processor processes, there must be a method with the exact same name as the Task name specified in the mapping XML file.
Defining a Task-Handler mapping does not completely prevent the enterprise service module from offering any services on its own. For instance, in the case of a small but highly useful service such as the AddressValidation service, it may not be required to designate a separate object to receive the client request. In such cases, the service provider may intend to process the service on its own with the help of other utility or helper objects. Such a service provider needs to implement a method with same name as the Task name. In addition, the Task-Handler mapping must specify the processor as itself.
Because each of the enterprise service components needs to provide the same implementation to read the corresponding Task-Mapping XML file, it is likely to result in duplicate implementation across enterprise service modules. To avoid this problem, the EnterpriseService can be designed as an abstract class with the implementation to read the mapping XML file into memory. This requires each of the service modules to be redesigned as subclasses of the Enterprise- Service class (Figure 45.7).
Figure 45.6 Client Accessing a Task-Handler Object
Throughout the life of the application there will be a need for only one copy of the mapping data and it needs to be available to all service modules and it should not result in any concurrency problems. To accommodate these requirements, when the mapping XML file is read, mapping details can be stored inside a Common Attribute Registry where each service can be treated as a CARGroup with Task-Handler values as individual name-value pairs within the CARGroup. This allows the storing of the same Task-Handler combination across multiple service modules.
The method signatures of both the service module’s execute() method and the handler’s process method indicate that they return a string back to the caller. Any errors that occur during the processing can be communicated back to the caller in the form of an XML string. The caller can parse the returned XML string to check for specific errors and carry out any required error handling.
In accordance with the application framework discussed earlier, every service module needs to define an interface contract to be used by the clients that intend to access its services.
Figure 45.7 Service Module Class Hierarchy with Each Module as a Subclass of a Common Parent Class
Using the enterprise services architecture discussed above, the address validation can be designed as a service module subclass of the EnterpriseServices class. Using the interface contract, clients can send the address to be validated to the service module. The service returns the validation results by way of the response structure defined in the interface contract.
When the service module receives a validation request, it can be forwarded to a designated handler, ValidationHandler instance. From the technical requirements section, it can be seen that there exists a utility to validate Canadian addresses that has a method name which is not in conformance with KPS IT standards. While it is possible to create an address validator for U.S. addresses with the method name following the naming standards, it may not be possible to alter the existing utility to validate Canadian addresses to follow the naming standards. Without having to recreate the utility from scratch, using the Adapter pattern, an adapter can be designed to make it possible to leverage the existing utility functionality. This same procedure can be used for validation of Asian addresses if there exists a predefined validation module that may not have a compatible interface.
The ValidationHandler can be designed to make use of a designated method to accept a country code and return the appropriate address validator. Once the validator is received, the handler can access its service in a seamless manner irrespective of the concrete class of the validator that is returned.
The two services that the credit card service offers are credit card validation and credit card charging. Let us design the credit card validation functionality here.
From the business requirements section, it can be seen that the system should have the ability to validate Visa, MaterCard, Discover and Diners Club cards. All of these cards have a definite set of steps in checking their validity. Some of the steps in validating these cards are identical across all these cards while some are carried out differently. Using the Template Method pattern, the outline of the validation steps and the common invariant parts of the overall algorithm can be kept inside an abstract class leaving the implementation of variant parts of the algorithm to its subclasses (see the example discussion in Chapter 38 — Template Method).
The search management should allow users to search for resellers, customers and Web sites. For performance reasons only a limited set of search results are to be displayed on the screen. Such parameters can be specified using the Constant Data Manager. The necessary UI panel can be designed using the Builder pattern (see the example in Chapter 14 — Builder Pattern) where each concrete Builder is responsible for creating the necessary UI for allowing a user to specify the search criteria. This allows the same series of steps to construct the UI panel specific to a search type. This gives the flexibility to use the same UI building logic when a new search, say an employee search, is to be added to the system. Without altering the existing implementation, a new concrete Builder specific to the employee search user-interface can be designed.
One of the requirements is to select an item from the result set on the screen and view more details on the selected item. Whenever an item is selected, a database query can be executed to retrieve its details. One of the ways of improving the performance is to introduce some amount of caching where some of the most recently accessed item details are kept in the memory. This can be designed as an Object Cache using a Common Attribute Registry (see the example discussion in Chapter 29 — Object Cache). Whenever a search results item is selected to view more details, item details are fetched from the cache, if it exists. Otherwise, details are fetched from the database for display and are also stored in the cache for later access.
Let us design one of the important tasks to be supported by the customer management service, the creation of a new customer profile. From the business requirements section above, it can be seen that the application must function even if the database is down. This means that the application must have the ability to write to files as well as to the database.
The functionality to save data to a file and the database can be encapsulated in two different classes — FileManager and DBManager, respectively. To allow client objects to deal with both the FileManager and DBManager in a seamless manner, they can be designed as implementers of a common interface Persis-tenceManager to provide the same interface to its clients. By virtue of poly-morphism, client objects will be able to treat both the implementers as the PersistenceManager type.
Instead of having every client object to dealing with the choice of instantiating either FileManager or DBManager, the implementation of this decision logic can be kept inside a separate Factory Method. If there is a change in the way one of the PersistenceManager objects is to be selected, the Factory Method can be overridden in a subclass of the class that contains the Factory Method.
One of the important steps in creating a new customer profile is the selection of a hosting plan for the customer Web site. From the “Business Objects” section, it can be seen that there exist two families of hosting plans — Windows and UNIX. Instead of requiring client objects to be aware of the existence of the families of concrete classes (such as WinBasic, WinPremium,…, UnixPremium and UnixPremPlus) and have the knowledge of the concrete HostingPlan class to be instantiated, the Abstract Factory pattern can be used to encapsulate these details into a set of concrete factories that share a common interface. Clients can get access to an appropriate HostingPlan instance using the common interface without having to know the class type of the object returned.
In addition to the hosting plan selection, the customer profile creation involves address validation, credit card validation, account creation and saving customer data to the database or file. For address and credit card validation, the handler makes use of the AddressValidation and the CreditCardService enterprise services, respectively. While performing these lower-level tasks, the handler presents a very high level interface to its clients. In other words, client objects do not need to directly deal with the details of creating an account, validating addresses, credit card information and saving the data using an appropriate PersistenceManager. A client only needs to formulate its request to create a new customer profile in accordance with the interface contract defined by the CustomerManagement service and forward the request to the CustomerManagement enterprise service. In this aspect the handler functions as a Façade object.
The FileManager can be designed to make use of a utility to write data to the file. While designing the utility to write data to the file may seem like a trivial task, it requires special care to ensure that the utility functions as desired in a multithreaded environment. Applying the concept of the Critical Section ensures this by allowing only a single thread to access the utility method to write to the file. Such a utility can be used in other parts of the application for other purposes such as logging messages. From the requirements section, it can be seen that when the data is written to the file, it must be encrypted to avoid easy exposure. It may not be a good idea to alter the utility to implement the necessary encryption to the data being written. This is because encrypting the data before writing to a file may not be required everywhere the utility is used. As a work around, a Decorator object may be designed to provide the necessary encryption implementation before sending data to the utility to write to the file.
As part of the preceding discussion, we have designed:
Interested readers may enhance the business requirements and design the rest of the system.
3.137.41.205