- Overview
- Chained Service Factory
- Unchained Service Factory
- Product Manager
- Service Façade
- Abstract Packet Pattern
- Packet Translator
Intent
Provide a single Web service method to allow the provider the flexibility of invoking different business functionality without directly affecting Web service clients. Provide a single entry point into a complex business framework.
Problem
Web service providers will eventually begin to provide Web services that go beyond accepting simple requests or providing simple responses. Not that Web services themselves need to complicated, but if they intend to be truly useful, the data sent to and from these services can become sophisticated. This pattern addresses the need for providing a Web service that not only performs a useful business function but also provides these services in a very flexible manner for invoking any business function. The single entry point is one of the first steps in defining what Microsoft has coined as a service-oriented architecture.
As providers begin to deploy these services, there will also be a need to change the logic behind those services. There needs to be a way to isolate the Web services client from these changes due to new features or altered implementations. Although no Web client can always be fully protected from future Web service functionality, the interfaces that they bind to should remain somewhat steady. The same goal of providing a standard interface “contract” to bind to (used in a more traditional implementation) can also be applied to the design of Web service methods. The Web service method can be thought of as just another interface contract to adhere to and, thus, should be generic enough to facilitate those inevitable implementation changes. This is the distinct difference between services and APIs. Services, or more specifically, serviced-oriented architectures, should be based on messages, not remote procedure calls (RPCs). The trick to designing this type of architecture is to focus on being service-oriented and not object-oriented at this service entry point. You'll have plenty of opportunity to apply OO heuristics to the inner plumbing of your service architecture later.
We solve this problem the same way we solve the problem in a typical OO application—by fashioning a Web service method interface “contract.” The interface contract will then maintain its definition by accepting the same set of parameters (including the return values) for each Web client. The Web client just needs the interface definition of the Web method to invoke any business function within the framework supported by this Web method. Each Web client would then be protected from future changes while still allowing the Web service provider flexibility in changing its function implementations.
The challenge in providing this generic interface-based Web service contract is that the parameters passed in the Web method also must either be generic or their type representations must be dynamic. They must be flexible and instructive enough to be able to describe the functionality that a Web client wants to call. In essence, the Web service method must become both a “factory” and a “delegator” for locating and calling the requested business functionality. The requested functionality must be described not by the Web method but by the contents of the generic parameters passed.
We implement this as a single Web service method with a single signature—Execute(). The parameters passed to Execute() provide the routing information or “factory inputs.” By using a generic data type such as an ArrayList or a string containing XML, the Web service method can use those parameters to determine which business function needs to be created and called. The Web service method then becomes the “factory” for generating all requested business objects and becomes the single point of contact for all Web clients. In our example, all automated check processing or credit card authorizations function through the same Web service interface. The FinancialServiceFactory.Execute() is then called, whether it is a credit card customer requesting an authorization or a client requesting a credit report. Each request is funneled through the same interface and the same Web method. FinancialServiceFactory's job is to read the contents of the passed oData object, instantiate the appropriate FinancialProduct, and call the requested service. Figure 4.1 shows one possible implementation diagram of this pattern. To view the generic structure please reference Figure 4.2.
Figure 4.1. Service Factory implementation class diagram.
Figure 4.2. Service Factory generic class diagram.
This presents another problem, however. What type is generic and dynamic enough to describe any functionality that each Web client may require of the Web service? If you are thinking XML, you are close. If you are thinking of XML schema definitions, you are even closer. The answer is the .NET DataSet. How did we arrive at that type? Well, DataSets are the perfect complement to a generic, type-safe data packager that not only understands an XML schema definition but also provides the developer with the facilities to manipulate them easily. See the Abstract Packet section later in this chapter; for an even deeper understanding, reference Chapter 5 for the technology backgrounder on XML schemas.
Forces
Use the Chained Service Factory pattern when:
-
Business function interfaces may change in the future.
-
Multiple Web service methods are provided on the server.
-
Web service clients cannot be controlled.
-
Changing client code would be difficult.
Structure
Consequences
The Chained Service Factory has the following benefits and liabilities:
-
It provides a single entry point into the system. Due to the fact that only one method acts as the entrance into the framework, this allows greater control over who is calling into the system and more control over interface standards. It also provides a simple way of announcing services to the provider. Frameworks and services (especially within a large organization) are constructed all of the time. The key to selling them is providing a simple yet generic approach to calling the functionality.
-
Eases system monitoring. Because all traffic is routed through this single point of contact, monitoring the system becomes simpler. Using HTTP monitoring applications (such as IIS Loader) simplifies the developer's profiling efforts because system load can primarily be determined through this one entry point.
-
It isolates any Web client from Web method signature changes. As stated in the above section, it gives a single entry point into the system and a generic signature that allows future services to be provided without affecting the external interface. This eliminates unnecessary Web service client proxy generations from occurring.
-
It provides the ability to add future framework objects into the system. Related to item 3, this also allows future subframeworks to be added easily into the system. For example, if all back-end business logic could be controlled by a primary controller object (see the Service Façade section in this chapter), that controller would be the instantiated target of the Chained Service Factory object. The Chained Service Factory would only need to be passed the service (from the client), at which time it would instantiate and delegate the business logic to that “controller.” This places another level of abstraction and delegation into the model but it allows future “controllers” to be “plugged” into the system. This would allow not only changes to business functions but also additions to completely different sets of services. In our example, the primary set of business services involves credit card authorizations. For example, a scheduling system could then be built in the future. It could schedule any number of generic events, such as administration activities, batch reporting, etc. Adding another “free standing” scheduling façade into the system would now be much simpler. The Web client can then still activate this new set of functionality, using the same primary interface or entry point that was used for credit card authorizations.
-
Calling simple Web service-based business functions requires more setup. Although this design promotes the above benefits, it also introduces a bit of complexity when exposing “simple” functionality. Calling any Web services in this fashion involves preparing the parameters using a generic scheme. In our example, the data type I use happens to be a DataSet. This DataSet can be then populated with a least one row of information providing the metadata that can then be used to determine which business service and specifically which methods should be called. The metadata can be of any schema. That is the point. You now have the freedom to come up with any configuration of parameter data that you see fit.
Datasets do not have to be used but for our example, this seems to be the best and most flexible approach. The DataSet would be a single, generic, self-describing package that the Web client uses to request a specific back-end business function, using the Chained Service Factory as its entry point. The problem with designing such a sophisticated self-describing parameter is that now you are forcing the Web client to package its parameters for each and every call. This packaging occurs even for simple service requests unless you provide an entirely different Web method. For some cases, building a complex set of metadata commands and packaging them up just to call a simple business function via Web service may seem like overkill at times. It may be prudent to design a Web service method and separate interface for those cases. The designer must also take into consideration that passing complex types to Web service methods using a data type such as a .NET DataSet requires SOAP as the calling protocol. This means that the designer will not be able to test the Web service from a standard browser utilizing a simple HTTP GET or POST action and, thus, must create a custom test harness to test the Web service.
Participants
-
ServiceClient (CreditCard Customer)— A Web service client for the credit card customer. This becomes the Web service proxy.
-
Service (Financial Service Factory)— Contains a Web method that acts as a single point of entry into the system. This entry point unpackages the service request, instantiates the correct service (e.g., Service Façade), and calls a standard method on that service.
-
Façade (Financial Product)— Defines a factory method to route the business request to the appropriate product. This is the entry point for each set of business functionality. It may contain all the business rules for a particular business category or may further delegate business behavior. This is usually an abstract or implementation parent that can contain logic such as data object construction or data preparation code (see the Packet Translator section in this chapter).
-
ConcreteFaçade (Credit Card)— Implements the factory method for the business request. This entity optionally acts as a “controller” or Service Façade to other subordinate downstream business objects.
Implementation
The word chained in the pattern name comes from the fact that the instantiated service, which is created by the Web method, is early bound to that Web service. In other words, the Web method knows at design time what types it will be creating. When a credit card client calls Execute() on the FinancialServiceFactory, this method acts as a factory to any FinancialProduct derived class. The oData parameter passed can be of any generic data type, as long as it holds descriptive parameter data. Descriptive data refers to a form of “metadata” that can be used to describe what business functions the client wishes to invoke. The client's responsibility is to provide the metadata and package it is using with the rules defined by the Web method on the server. The metadata can take any form and be held using several generic types, as long as that type is flexible enough to contain this metadata and can be easily marshaled across the established invocation boundary.
In Listing 4.1, once the metadata is packaged and passed to the Web service, it is then used by the FinancialServiceFactory to instantiate the appropriate financial product—the CreditCard object in our example. The following code displays the signature of the Web method within the FinancialServiceFactory object. Notice the single parameter used to pass the data to the Web service. A .NET DataSet was chosen here due to its flexibility. A DataSet can then be used not only to contain this metadata but also to contain all of the actual data passed to the requested business function. The metadata can then be contained within a separate DataTable type as part of the DataSet. Other DataTables containing the actual data to be processed by the business function can also be passed without requiring the Web service to hold state between method invocations. All metadata and instance data is passed with one call to the Web method. The metadata is interrogated, the appropriate service is instantiated (see switch/case statement), and a standard method is called on that service. From that point on, it is up to the service (or FinancialProduct in our example) to disseminate it from there.
Listing 4.1 Service Factory method sample implementation.
[WebMethod] public DataSet Execute(DataSet dsPacket) { DataSet ds = new DataSet(); FinancialProduct oProduct = null; string sService; // call static translator method to extract the service // name for this packet sService = PacketTranslator.GetService(dsPacket); switch (sService) { case Constants.PAYMENT_SVC: oProduct = (FinancialProduct) new CreditCard(); break; case Constants.REPORT_SVC: oProduct = (FinancialProduct) new CreditReport(); break; default: return ds; } // invoke the DoOp factory method on the facade ds = oProduct.Execute(dsPacket); return ds; }
Like any factory, a switch case statement is used to instantiate and early bind the CreditCard product to the FinancialServiceFactory. The financial product is determined in this example by a string value passed in the oData parameter. More specifically, the string is passed as part of a metadata DataTable as part of the passed in DataSet (oData). The string is extracted from the Dataset's DataTable, as shown. A DAL (Data Access Layer) object is used to ease our work with the database (to see how the DAL is implemented, please reference Chapter 5. The data access specifics can be ignored for now; simply pay attention to the how the metadata column is accessed to extract the value of the service type. The returned string is then used by the switch statement above to determine which FinancialProduct to instantiate. Because both the CreditReport object and the CreditCard object inherit from the abstract type FinancialProduct, the lvalue in our case can be typed as the abstract type and its factory method called. Once Execute is then called on our instantiated FinancialProduct, that specific product can then disseminate the passed data as it sees fit. Listing 4.2 shows the helper method used to extract the service type from the incoming metadata.
Listing 4.2 Service Factory metadata helper method.
public static string GetService(DataSet ds) { DAL oDAL = new DAL(); string sValue = null; // grab first and only row of the meta table passed as part of // the dataset oDAL.Data = ds; object oTemp = oDAL[Constants.META_TABLE, "SERVICE_COLUMN"]; if (oTemp != null) sValue = (string)oTemp; else throw new Exception("..."); return sValue.TrimEnd(); }
For example, if the Credit Card customer passes CreditCard as the service, it is used to instantiate the CreditCard class from the FinancialServiceFactory. Where this pattern differs from a typical factory method (GoF) is that it uses a Web service method to invoke the factory. It also differs in that it uses a generic data type to hold any of the parameters passed from any Web service client (a credit card customer, in this case). The DataSet is that generic type. A DataSet was chosen due to the fact that it is SOAP-friendly and very dynamic in that it can hold just about any structured data. Using DataSets provides the architecture with the most flexible alternative. Another option would be to use an ArrayList or even a generic custom object.
A Service Façade pattern can be used to add yet another level of abstraction between the Financial Service Factory and the FinancialProduct objects. Instead of using the metadata to instantiate a FinancialProduct, a Service Façade instead could then be instantiated and called. The mechanism is the same as it is in this example, just with an added level of abstraction. This would provide the ability to plug any service into the framework at any time without affecting bound Web service clients. For example, a schedule system façade could be implemented and added to the framework. This would then allow any Web client the ability to call this new “broad-based” service using the same calling semantics as calling for CreditCard services. The client simply has to package the metadata as before except that now it will be requesting a different service. The only code that would be required to change is the switch case statement setup in the Web service method. This change is required due to the fact that .NET is inherently early bound. The class type must be known ahead of time (the Service Façade object, in this case) and must be instantiated specifically based on the metadata passed in by the Web client. In the next section, I show you another pattern, the Unchained Service Factory, that eliminates this early bound requirement. This is possible using .NET Reflection capabilities and will be explained in the upcoming technology backgrounder.
As you can see, this can be implemented with several levels of abstraction. The point and benefit to this is minimizing the impact on all of your current Web service clients while still providing your services the flexibility of adding new, more broadly based services to the framework at will.
Related Patterns
-
Factory Method (GoF)
-
Abstract Factory (GoF)
-
Unchained Service Factory (Thilmany)
-
Strategy (GoF)