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 CodeBecause 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.
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 InterfaceThe 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 useThis 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 :
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 methodpackage 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:
Example 7-2 shows the PurchaseBooks method. Example 7-2. The WishListCustomer::PurchaseBooks methodsub 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 VehicleAnother 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 . |