7.3 Designing the Server


What are the issues to keep in mind when designing this service? Given a known set of requirements that must be met, the design might start by taking into consideration the interface that will be made available to the outside world. That plan will shape the code that the SOAP server is eventually built upon. The SOAP server is more than just that, however. It will also have to look at the protocols by which to expose the API (which for now is just HTTP) and how to bind to those protocols.

7.3.1 Supporting Code

Because this is a book on web services and not on databases, the code for the underlying database layer isn't described in this chapter. It is in Appendix D with the rest of the application. For the exercise, assume that a different development group has provided the user and database code modules for integration, and that they work without any problems. [1] The interface itself is simple, and throughout the chapter the routines will be referenced by their full package, to further clarify things.

[1] Don't laugh , it could happen.

The underlying database of books is a simple DBM-derivative (using the DB_File module in the Perl core ) whose content is an abbreviated O'Reilly & Associates catalog. The primary key used to track books is the ISBN number. User records are keyed by the user "nickname" and also kept in a DBM-based structure. The interface to these databases consists of just the simple, minimal operations. Because the application isn't responsible for maintaining the databases or their contents, the routines mainly cover data retrieval (except for modifying the user's wish list of books). Because they aren't the focus here, the routines are only lightly documented as they're used.

7.3.2 Managing the Interface

The initial temptation might be to expose the reduced database API as the SOAP service itself. This would be a bad idea for a number of reasons, the main one being that even with the limited database access this API provides, there is almost certainly going to be information in the records that shouldn't be given out, at least not without careful consideration. Instead, a composite class that encapsulates the relevant information from a user record and from book records will be used. See Figure 7-1.

Figure 7-1. The composite class the server will use
figs/pwsp_0701.gif

This midlevel class makes it easier to encapsulate the connections to the separate databases, a detail that clients of the service have no need to know. It also makes it easier to export the functionality through the server without exposing other elements in the process.

Going at this from the angle of an object-oriented design, the operations listed earlier that aren't user-specific in nature (simple searching for books by author or title and pulling a limited range of information on a specific book) might be treated as class methods , because they don't require a specific user object to invoke them.

Calling the new class WishListCustomer , there are three static methods to start off the implementation: BooksByAuthor , BooksByTitle , and GetBook . As will be shown later, that third method is combined with the normal retrieval of book information such that it decides how much information to return based on whether it is called as a static method or with a valid object instead.

These methods can be initially documented as follows :

BooksByAuthor

Input parameter: search_for , a string.

Return value: array (reference) of ISBN-based keys.

Search for books whose author field contains the substring given in the author parameter.

BooksByTitle

Input parameter: search_for , a string.

Return value: array (reference) of ISBN-based keys.

Search for books whose title field contains the substring given in the title parameter.

GetBook

Input parameter: isbn , a string.

Return value: structure (reference) of book information, or undef .

Given a ISBN-based key (the ISBN minus all hyphens), return structured data pertaining to the book. As a static method, this is limited to the isbn , title , author , and url fields.

In these cases, the returned data is managed at the Perl level by references to more complex datatypes (lists and hash tables). The SOAP serialization manages their expression as proper XML Schema-based data. The code in Example 7-1 shows one of these methods, BooksByAuthor , as it appears in the WishListCustomer class.

Example 7-1. The WishListCustomer::BooksByAuthor method
 package WishListCustomer;     sub BooksByAuthor {     my ($class, $author) = @_;         my $bookdb = ref($class) ? $class->{_catalog} :                                SoapExBook::connect( );     return undef unless $bookdb;         my @books =         SoapExBook::get_books_by_author($bookdb, $author);     \@books; } 

The code is simple, and the code for BooksByTitle will look very similar. The two could in fact be implemented as the same function as a means of choosing how to do the search, using differently named parameters and the SOAP envelope to determine if the argument was named "author" or "title." This will be revisited later, when server-side access to the SOAP envelope is discussed.

Handling the user-centric operations is a little more involved because a valid object is expected from which to derive some of the data. One operation has already been defined: GetBook . That method will be the same as an object method as it stands as a static method. The difference will be in the amount of information returned in the structure. It will also be necessary for the class to have a constructor method.

Once a client has a WishListCustomer object, it calls methods on that object to obtain the wish-list contents and add to or remove books from the wish list. There's also a method to inform the client whether or not the user can directly purchase books.

The full list of methods for the WishListCustomer class follows:

new

Input parameters: user and password , strings (optional).

Return value: A new object reference, undef if loading user failed.

This will be the constructor (using the traditional name). It creates an instance of the class, and if a user's name is given as a parameter, it preloads that user's information.

SetUser

Input parameter: named parameters in the form of key/value pairs.

Return value: object reference or an error message.

If the object isn't initialized with a user with the optional parameters to new , this method allows the client to specify the user record to load in. This allows for the client and server to negotiate more complex authentication methods. Currently supported keys are user , and either password or cookie .

Wishlist

Input parameter: none.

Return value: array (reference) of structure (reference) elements.

This retrieves the current wish-list contents as an array. Rather than just returning the ISBN-derived keys the system uses to reference books by because almost any application would need more information on the books immediately, the method returns structures that contain the ISBN, the title, and a URL link for the book. The ISBN can fetch a fuller set of information on a given book.

AddBook

Input parameter: isbn , a string.

Return value: object reference upon success, error message on failure.

This adds the book given by the ISBN-based key to the wish list. It checks that the key refers to a book in the catalog and returns a SOAP fault if it isn't.

RemoveBook

Input parameter: isbn , a string.

Return value: object reference upon success, error message on failure.

Removes the specified book from the wish list. It is silent in those cases where the passed-in ISBN isn't on the user's wish list.

CanPurchase

Input parameter: none.

Return value: boolean .

This returns a true / false value that indicates whether the user has enough information on file to be allowed to directly purchase books from their wish list. To Perl, boolean isn't as significant as it is in languages such as Java. It is areas like this that SOAP is at its best in interoperability.

PurchaseBooks

Input parameter: array (reference) of keys, or just isbn as a string.

Return value: object reference upon success, error message on failure.

As a convenience to the user (and a boon to the accounting department), this method makes it possible to buy books directly. It takes one ISBN key or an array of them, and takes care of whatever business-enterprise steps are necessary to start the process of billing the customer and shipping him the new books. The titles are removed from the wish list if the operation is a success. If it isn't, they are left in place, and a SOAP fault is returned.

Example 7-2 shows the PurchaseBooks method.

Example 7-2. The WishListCustomer::PurchaseBooks method
 sub PurchaseBooks {     my ($self, $list) = @_;         return 'Object is missing user data'         unless (ref($self) and my $user = $self->{_user});     return 'User cannot make direct purchases'         unless ($user->can_purchase);         # Handle a single ISBN as just a one-item list     my @books = ref($list) ? @$list : ($list);         # Here would normally be lots of convoluted glue code to     # interacts with enterprise systems, CRM, etc. For this     # example, just remove the books from the wishlist and     # update the user.     $user->drop_book($_) for (@books);     $user->write_user;         $self; } 

This illustrates a few noteworthy elements about how the mid-level container class is handling the encapsulated data. For one thing, the method checks that it wasn't called as a static method (the ref($self) test on line 5). Also, note that most of the methods on the $user object are similar to ones in this class, but the local class versions aren't being used. Because each local method starts with the validity test, this means a lot of redundant testing. In the case of the for loop that calls the drop_book method, this can be quite a lot of redundancy. The disadvantage to this, however, is that it hinders future efforts to subclass this class.

This leaves only one area to be addressed: how to manage a cookie-based authentication. For the sake of simplicity, the WishListCustomer class uses the same MD5-based algorithm the use Perl; journal system uses, described in the previous chapter. This class copies the make_cookie routine from that example, and the SetUser method of this class is responsible for handling and testing the cookies it receives using this code. Later, when some sample clients are illustrated , some may use WishListCustomer::make_cookie to create authentication credentials. Simple as it may seem, this defines the interface as far as the SOAP server is concerned .

7.3.3 Choosing the HTTP Vehicle

Another factor to consider in designing a server like this is how exactly to bind it to a HTTP transport. The SOAP::Lite package offers a variety of ways to do this, so how does a project select the best-suited solution for the application?

Like most questions of that nature, there isn't a specific answer that fits all situations. It is because of the variety of environments and situations that such a range of choices exists. In the case of this project, part of the decision-making process is already provided in that the finished service needs to integrate with an existing system, the book database. It is often the case that web services are required to accommodate elements that are already deployed. These preexisting components naturally affect the design of the web service.

A good rule of thumb when designing these services with Perl in mind is to favor the combination of Apache and mod_perl . With these, you use the SOAP::Transport::HTTP::Apache class, either directly within a handler package for mod_perl , or through use of the Apache::SOAP wrapper that is a part of the distribution. Given the widespread acceptance of these technologies, it is a good chance that Apache, at least, is already in use. If Apache is being used, but mod_perl isn't available (and installation isn't an option), the SOAP::Transport::HTTP::CGI class is your next best option, unless the server is configured with the FastCGI protocol, in which case SOAP::Transport::HTTP::FCGI is available.

If there isn't a web server already in use on the target platform, and the web service isn't expected to endure high levels of load, then it may be enough to just use the SOAP::Transport::HTTP::Daemon class and deploy a small standalone server application that only handles the SOAP transactions and nothing more. Even in some cases where there is another web server already in use, this may prove a reasonable choice, because it frees the SOAP server from any maintenance- related issues with regards to the regular HTTP server, and vice versa.

Each of the choices has benefits and drawbacks. The Apache solution is likely to be the fastest of the bunch, but it requires mod_perl to be the most effective. As popular as that environment is, if it isn't available, the application has to evaluate other choices. Additionally, using it in that environment can expose the code to other parts of the server (through virtual hosts ) that aren't meant to have access to it. The CGI approach doesn't have the performance of Apache, but it can be used under Apache::Registry as an alternative to the other model. Plus, it can be used under web servers other than Apache. The server based on the SOAP::Transport::HTTP::Daemon class would likely be the least-efficient in terms of performance and request throughput, but it has the advantage of being fully self-contained.

For this example, the initial implementation uses the standalone HTTP class. A later section adapts it with a location handler for use under Apache mod_perl .



Programming Web Services with Perl
Programming Web Services with Perl
ISBN: 0596002068
EAN: 2147483647
Year: 2000
Pages: 123

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net