I’ve been working with SOAP services a lot lately. Is that moaning I hear? It is! But you know, as long as I’m provided an API that works, and isn’t *too* slow, I’ll deal! However, it’s nice everyone finally realized XML.. well.. isn’t the best for stuff like this, and are moving away from it. Took them long enough.
This should be a relatively quick post, but I think the point it makes is nice. My colleague Paul once told me my code looks like a 5 year old wrote it. I try to keep things simple, and make things simple. The goal is to take something that could have been complex (or sounds complex), and break it down into little logical chunks you can make sense of the first time through. I’m also a 5 year old, so that might have something to do with it.
In this case, I was working on interacting with a SOAP API for a load balancing product. Their API is actually really nice, but with the amount of settings you can change for certain objects, their free, open code samples definitely wouldn’t fly for an enterprise-class tool. Here it is:
Original Code
my $result = $soap->addservice( SOAP::Data->name( 'name' => 'test-svc'), SOAP::Data->name( 'IP' => '1.2.3.4'), SOAP::Data->name( 'servicetype' => 'http'), SOAP::Data->name( 'port' => '80'), SOAP::Data->name( 'maxreq' => '8'), )->result();
If this is all you need to do, then it definitely works. But, what happens if you need to set more than 5 values? Maybe you have 10 you need to set? Maybe the number of arguments is going to be dynamic every time you run it (ie: you don’t know if maxreq is going to be set EVERY time). Well, that could be a pain in the bootay.
There are a couple things we’re going to end up doing here, but first thing first: this whole multi-argument thing isn’t going to fly if I want to make things dynamic (ie: loop through a data set to build your input key/val pairs). I also reeeaaally don’t want to have to say SOAP::Data->name for every key/val pair I set. I’d rather just be able to define my arguments as a hash (or dictionary, or whatever your $langauge calls it), and let something take care of the dirty work for me. Simple!
Since our SOAP call takes an array of SOAP::Data objects, all we really need to do is make something that takes a hash, converts it into an array of SOAP::Data objects, then pass to our SOAP call. We’re going to use an arrayref to make things easier. I think pointers are fun, so I tend to just use ref’s for everything. I hope that’s okay.
sub hash_to_soap_data { my($hashref) = @_; my $soap_data = []; foreach my $key(sort keys %{$hashref}) { push(@{$soap_data},SOAP::Data->name( $key => $hashref->{$key} ); } return $soap_data; }
Ta da! That wasn’t so bad, was it? Now, we can do something like this:
# we could also just define the hash # as the argument to hash_to_soap_data # if you wanted to smash it together even # more. my $hashref = ( name => "test-svc", servicetype => "http", port => 80, ipaddress => "1.2.3.4", ); my $soap_data = hash_to_soap_data($hashref); my $result = $soap->addservice(@{$soap_data});
Looking better! But you know, it’s still a little bit too annoying. I don’t want to have to define a hash, pass it to a function X, then take the result of function X and pass it to function Y. The ideal code would just to be to make an API call by passing a hash to a function, and let the underlying code handle making things happen. In short, we’re going to abstract things.
This is where we decide how we want to actually interface with our function. In our case, I think something like this is nice:
my $result = call_api($func_name,$hashref);
With that example, we can just make our API call in one easy line by passing a hashref. Yes, we’ll have a function call_api() to deal with, but that’s part of the abstraction.
Ultimately, this will eventually allow us to easily change the inner-workings of call_api() later on without ever having to change the rest of our code. For example, maybe the API changes from SOAP to a RESTful style, maybe using JSON. By abstracting, you can re-write call_api() to just make RESTful calls and return JSON results.
In any case, let’s write it:
sub call_api { # note: $soap is our SOAP or SOAP::Lite object # that we have already defined elsewhere. my($func,$hashref) = @_; my @soap_data; foreach my $key(sort keys %{$hashref}) { push( @soap_data, SOAP::Data->name( $key => $hashref->{$key} ); } my $result = $soap->$func(@soap_data); return $result; }
With that complete, now we just implement:
Abstracted Code
my $result = call_api( "addservice" , { name => "test-svc", ip => "1.2.3.4", servicetype => "http", port => 80, maxreq => 8, });
Your first thought probably is: it looks almost exactly like our original code! What’s the point? You suck? The point is you have now abstracted things just enough to make things a lot easier to manage in the long run. Based on inputs, you can dynamically assign the function (ie: maybe it’s addservice to start, but based on a conditional, it may change to setservice, and all you need to change is $func_name. Your single API line still works as expected, so no need for multiple API lines. Same with your data hash. You can build your input key/val pairs manually, dynamically based on IO, other conditionals, etc, and never have to change that one friendly line:
my $result = call_api($func_name,$hashref);
It also helps with troubleshooting, because you know that all API calls ultimately funnel in to call_api(). And, like I mentioned before, if the server-side ever changes from SOAP to whatever the latest and greatest is, you should have hopefully abstracted things enough to just be able to change your single function without affecting everything else.
In practice, I’ve found this to work pretty well. There will be one-offs now and then, and you’ll have to account for them. However, over time, you’ll start to be able to identify places for abstraction beforehand, which in the long run will just help you to make pretty sweet abstractions for doing anything. This doesn’t just apply to Perl, and it doesn’t just apply to SOAP. I try to apply it to as many places I can. It tends to work best in an object-oriented codebase (yes, even Perl objects), but even abstraction via functions can be good too!
Someone joked once about “hello world” for a new programmer is 1 line, while it’s 250 lines with 5 layers of abstraction and an additional 20 comments for an experienced programmer. While it’s not far off, there’s definitely a sane balance somewhere in the middle, and that’s part of the journey
Enjoy!



