Showing posts with label XML::Compile::SOAP. Show all posts
Showing posts with label XML::Compile::SOAP. Show all posts

Nov 7, 2012

Business::CyberSource API is stabilizing as of 0.7.x

Business::CyberSource (BC) has been going through API changes for a while now. If you're using it you've probably noticed this and wondered why? The reason behind it was when I first made it I wanted it to be extremely simple to use, and I was realistically a Junior programmer. Over the past year I learned a lot about API design and Object Oriented Programming, as well as payment gateways and credit card transaction processing. From the first production ready release I knew that it had design problems due to a large quantity of duplicated code, but at the time I didn't know how to get rid of it.

Domain Driven Design

My first refactor used some principles I learned from Domain-Driven Design: Tackling Complexity in the Heart of Software I changed the design of the objects to be more clear for experts in CyberSource. I also started restructuring them to more closely match the remote model. This meant making Requests and Responses to be made up of nested objects (Responses were only done as of 0.7.x). Another Change regarding this was to rename the submit method to run_transaction which is the name of the Remote Procedure call that is executed.

Composite Design Pattern

By making the Requests up of nested objects it allowed me to use the Composite Pattern from Design Patterns: Elements of Reusable Object-Oriented Software to serialize all of the objects into a simple nested hashref that XML::Compile::SOAP expects. Moving to these smaller objects that could serialize allowed me to also add more offline tests.

Dependency Injection in Tests

Most of the tests for BC start out exactly the same, except for one change, the value in the amount part of the Credit Card Authorization. This is because CyberSource's Test API uses special amounts to allow you to test getting different responses. e.g. something like (I'd have to look it up) 5000.05 is maybe a REJECT with a special processor code and cv code. Because of this I wired up my tests using Bread::Board to reduce the amount of boilerplate code in all tests that require actual remote interaction.

The Impact

Ultimately changing my Remote Facade to make use of more design patterns and be designed after it's niche domain has allowed me to have both simpler, deduplicated, more robust, more correct, and easier to modify code. Some things were not possible in earlier versions, or would have been incredibly complex to add. Now it'll simply be an issue of adding a Moose attribute to add a feature present in the WSDL. Before certain calls could not return all of information that was in the actual SOAP response, now everything should be accessible.

Possible Bugs

One of the possible bugs of this last refactor is that I use MooseX::StrictConstructor for all of my Moose objects. It is possible now that the XML::Compile::SOAP hash is simply passed to the Response Object that if a key I didn't account for were present that an exception would be thrown. If this is thrown on anything other than a 102 Invalid Field response, then it is a bug in BC and should be reported. I could have turned StrictConstructor off on the responses, but I believe that throwing the exceptions may ultimately make BC a better library. Also with a test suite that totals over 1400 (including generated generic ) tests, I'm fairly confident that there will be no problems in production.

New Debugging

In order to aid in finding bugs and diagnosing problems when they happen cybersource now has 2 debug setting levels. These can be set by having debug be 0 (off), 1 (request/response hash), 2 (full soap payloads). These should not be turned on in production and because they will print out Credit Card numbers.

In Trial

Currently I've left BC v0.7.5 in trial, but barring any bugs being reported, or cpan testers tests failing, I'll probably release v.0.7.6 as stable early early next week.

Stable API

I do not forsee any more major API changes in the future of BC, all of my original problems have been weeded out. This means I'll be able to focus on features and documentation with future work. It is possible that some changes to exceptions and error handling may happen, but I don't see that being a big issue.

Jun 4, 2012

New Module: MooseX::RemoteHelper (RFC)

Background

I have spent much of the last year writing and refining Remote Facades. At this point I've worked with SOAP, REST/JSON, and RPC url-form-encoded API's. One of the hardest parts I've found is dealing with the serialization of a Data Transfer Object with a Perl interface into whatever the remote is expecting. When I started I didn't know of these patterns, or really anything about these patterns. I highly recommend reading Patterns of Enterprise Application Architecture) if you want to know more about these patterns or things like Active Record and MVC.

problem

The problems I've encountered are many, including the fact most remotes are buggy or have a cludgy interface. Though there's nothing you can do about a remote api that you don't control, you can make your local API much cleaner and more native. Doing this however comes with a few challenges. One is that you have to map a local attribute name to a remote attribute name, because Perl uses underscores, and Java uses camel case, e.g postal_code and postalCode. The second problem is that many times the value of the attribute in its perl native form is not what the remote wants, e.g. perl boolean "1" remote "Y", or a DateTime object to W3C formatting. This second is not quite the same as mapping, because mapping is one to one, this translation could be turning an array into a comma separated string. The third problem, I didn't run into until after I "solved" the first , is how should I deal with nested complex objects (one's that can't be just converted to just a string).

My first naive remote facade was very procedural and simply assembled top to bottom, in part because it was based on SOAP::Lite, and in part because I had yet to figure out a better way. This resulted in a giant unwieldy if/then chain. Obviously my translations were just inline too.

The next thing I tried was using triggers to construct a request hash to pass to XML::Compile::SOAP. This worked better as the hash constructing code was kept right next to the attribute, so if I needed to modify the local or remote attribute, I could just go look at the attribute and the trigger tied to it.

After that I tried to use a map to translate from the native attribute name to the remote attribute name. This may have been more successful had it worked more like the Assembler in the Remote Facade. But ult imately since we were developing a rapidly changing API it seemed to bog me down, this is because I was changing the attributes on both sides of the mapping and thus the mapping at the same time (so at least 3 places). Here I was just manually dealing with the translation from a W3C DateTime format to the object I needed.

a solution

When I got assigned to yet another API and found myself doing yet another mapping and translation I decided that I needed to solve the problem. Enter the first iteration of MooseX::RemoteHelper. The first tie I used it with the form-url-encoded API so it was only needed for a single level of key, value pairs. .

Once I determined how to create MX::RemoteHelper it was simply a matter of using Class::MOP::Class API's to iterate all the attributes. The source of CompositeSerialization will give you some idea of how I did this.

Then I went back to apply this to a previous module, because the technique appears to be cleaner. Unfortunately I ran into a problem, this other API was a complex data structure, and how best to provide nested hashrefs and arrayrefs. Though I was now armed with Patterns I didn't know of one that would solve the problem. Fortunately a quick flip through the Gang of Four brought me to the Composite Pattern. I used this to write the recursive CompositeSerialization so that if I had a sufficiently complex nested structure I could simply create another object to deal with that. Here's a full example:

use 5.014;
use warnings;
use Data::Dumper;

package MessagePart {
    use Moose;
    use MooseX::RemoteHelper;
    with 'MooseX::RemoteHelper::CompositeSerialization';

    has array => (
        remote_name => 'SomeColonDelimitedArray',
        isa      => 'ArrayRef',
        is        => 'ro',
        serializer => sub {
            my ( $attr, $instance ) = @_;
            return join( ':', @{ $attr->get_value( $instance ) } );
        },
    );

    __PACKAGE__->meta->make_immutable;
}
    
package Message {
    use Moose;
    use MooseX::RemoteHelper;

    with 'MooseX::RemoteHelper::CompositeSerialization';

    has bool => (
        remote_name => 'Boolean',
        isa      => 'Bool',
        is        => 'ro',
        serializer => sub {
            my ( $attr, $instance ) = @_;
            return $attr->get_value( $instance ) ? 'Y' : 'N';
        },

    );

    has foo_bar => (
        remote_name => 'FooBar',
        isa      => 'Str',
        is        => 'ro',
    );

    has part => (
        isa      => 'MessagePart',
        remote_name => 'MyMessagePart',
        is        => 'ro',
    );

    __PACKAGE__->meta->make_immutable;
}

my $message
= Message->new({
    bool    => 0,
    foo_bar => 'Baz',
    part    => MessagePart->new({ array => [ qw( 1 2 3 4 ) ] }),
});

say Dumper $message->serialize

Which should give you this data structure:

Request For Comment

I've recently released a Trial version of MooseX::RemoteHelper to CPAN. I'm currently refactoring Business::CyberSource to use it, and it appears to be solid. What I'd like to know what people think of the module names, method names and any other comments they might have. I haven't been entirely sure that I've been naming things correctly while writing this, or that the code couldn't be better in other ways. If there's functionality you wish it had but doesn't let me know.