desoto has asked for the wisdom of the Perl Monks concerning the following question:

I humbly seek wisdom from the Perl Monks:

I'm attempting to write a SOAP server with SOAP::Lite that exposes classes written using Moose (specifically MooseX::Declare). The concept of Perl SOAP clients using SOAP::Lite is not new to me, and I have been able to successfully build a simple SOAP server using the examples in the SOAP::Lite documentation. However, I am not able to translate this limited knowledge to my Moose based classes.

The purpose of my SOAP server is to expose an API my company uses to normalize address data. While the API expects the data in the form of a hashref, I have the same problem passing simple data types to the methods.

The server:

use strict; use API::Translator; use SOAP::Transport::HTTP; SOAP::Transport::HTTP::CGI -> dispatch_to('API::Translator') -> handle;

The Client:

use strict; print "Content-type: text/html\n\n"; my $client = SOAP::Lite->new( uri => 'http://www.example.com/API/Translator/', proxy => [ 'http://sandbox.example.net/api/', credentials => [ 'sandbox.example.net:80', # hostname:port 'EXAMPLE', # realm 'user' => 'password' # username => password ] ] ); my %address = ( 'line1' => '1234 Main Street', 'line2' => 'Apartment X', 'city' => 'Richmond', 'state' => 'Virginia', 'zip' => '23220' ); # Regardless of how or what I pass, I still get the same error: my $result = $client->normalize(SOAP::Data->name('address')->value(\%a +ddress)); #my $result = $client->normalize(\%address); #my $result = $client->normalize('HEY!'); unless ($result->fault) { print $result->result(); } else { print join ', ', $result->faultcode, $result->faultstring; }

The class:

use MooseX::Declare; class API::Translator { use strict; use warnings; has 'normalizer' => ( is =>'rw', isa => 'Address::Normalizer'); # This method throws an error regardless of what I pass it. The er +ror is thrown on the line that defines the method itself, so it doesn +'t matter what code is inside the method. We never get that far. method normalize($address) { require Address::Normalizer; # Initialize the services needed to normalize addresses. $self->normalizer( Address::Normalizer->new->connect('127.0.0. +1', '56789') ); $self->normalizer->normalize($address); return $self->normalizer->response(); } # This function from the SOAP::Lite documentation. This returns d +ata without error. sub f2c { my ($class, $f) = @_; return 5/9*($f-32); } }

The error (paths truncated):

soap:Server, Validation failed for 'MooseX::Types::Structured::Tuple[M +ooseX::Types::Structured::Tuple[Object,Defined],MooseX::Types::Struct +ured::Dict[]]' failed with value [ [ "API::Translator", HASH(0x92c11f +c) ], { } ], Internal Validation Error is: Validation failed for 'Moo +seX::Types::Structured::Tuple[Object,Defined]' failed with value [ "A +PI::Translator", { city: "Richmond", line1: "1234 Main Street", line2 +: "Apartment X", state: "Virginia", zip: 23220 } ] at [...]/MooseX/Me +thod/Signatures/Meta/Method.pm line 443 MooseX::Method::Signatures::M +eta::Method::validate('MooseX::Method::Signatures::Meta::Method=HASH( +0x8f0e660)', 'ARRAY(0x8e603b4)') called at [...]/MooseX/Method/Signat +ures/Meta/Method.pm line 145 API::Translator::normalize('API::Transla +tor', 'HASH(0x92c11fc)') called at [...]/SOAP/Lite.pm line 2797 eval +{...} called at [...]/SOAP/Lite.pm line 2782 eval {...} called at [.. +.]/SOAP/Lite.pm line 2748 SOAP::Server::handle('SOAP::Transport::HTTP +::CGI=HASH(0x91a5170)', '

My first reaction was that MooseX::Types (or derivative) doesn't like the structure of the address hash being sent to it. I spent the better part of a day playing around with setting data types in the class and forcing the SOAP client into different data types. Regardless of what was sent, I'd get some variation of the message above. Even when attempting to send and return a simple string.

What confuses me is that the f2c function works. When I change it to:

method f2c ($f) { return 5/9*($f-32); }

however, I get the same error as above. So I'm pretty confident in saying that the problem is not so much with the data types, but with the order of the expected types, or with how the SOAP server code calls the method itself. Something to do with the method construction rather than its implementation. I haven't a clue whether or not my hunch is true. I'm new to Moose. I'm new to SOAP servers. I'm out of ideas.

Good Perl Monks: please steer me in the right direction. Where am I going astray?

Replies are listed 'Best First'.
Re: SOAP::Lite and Moose. Moose and SOAP::Lite
by stvn (Monsignor) on Nov 12, 2009 at 03:30 UTC

    You have uncovered the ugly guts of MooseX::Declare here.

    One of the biggest issues with MooseX::Declare (IMO at least) is that it exposes too much of it's internal workings with the error messages which makes them really difficult to read and understand (this will eventually get fixed, but it is still a relatively new module). As best I can tell it looks like your calling normalize as a class method and not as an instance method. You might want to try changing the method signature for normalize to

    method normalize(Str :$class, HashRef $address) { ... }
    So you are being more specific about what you expect the invocant to be.

    It should be noted that MooseX::Declare is still pretty cutting edge stuff, you might have more success starting with plain Moose first, then porting to MooseX::Declare once you have the details worked out.

    -stvn

      I wish my project wasn't on such a tight deadline, because I'd really like to get to know this MooseX::Declare. There's obviously a lot I don't know about it, but it seems like it's a tremendous simplification of the existing Moose object structure which is a tremendous simplification of the existing Perl object structure.

      I took your advice and backed off to Moose. What you said about class method vs. instance method is correct. Moving to Moose didn't eliminate that problem and honestly it took me several more hours to figure out that I wasn't going to solve that problem.

      I know that SOAP should allow initializing and calling objects as if the SOAP service were on the local machine. That was what I was attempting, however, I couldn't figure it out. So I devised a work around that I'm sure pure SOAP proponents would find appalling. But I reevaluated my requirements and determined that I could get by without this functionality.

      My SOAP::Lite server now calls a module that looks like this:

      package API; use API::Translator; use Moose; sub normalize { my ($class, $address) = @_; my $service = API::Translator->new(); return $service->normalize($address); }

      This pushes initialization of the API::Translator object behind the SOAP server allowing use to reference methods within that object without issue. It's not a solution I'm completely happy with, but it works.

      In retrospect, I'm sure there is a way I can bless the hashref of the object that gets passed through the SOAP server to avoid this middle man class, but like I said above, sometimes the timeline dictates the direction you go.

      I appreciate your direction. Thank you.