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.

Nov 3, 2012

Interface Driven Design

What is Interface Driven Design?

Interface Driven Design simply means that you should design your software around a flexible, easy to use, easy to understand interface. This is easy to achieve if your objects are of SOLID design. There is a simple table and reference link if you're not familiar with the principles.

My Work is SOLID already

Then you're on the right track but it's not enough if you don't fully marry the concept to best practices. I've seen quite a bit of work that's SOLID enough but fails to provide good interfaces.

Why is this so important?

Getting your interfaces correct is important because someone should be able to replace your code with new code, or subclassed code and it should still work.

Example: LWP::UserAgent and Mojo::Useragent

These two libraries do exactly the same thing, they provide an HTTP Client. However, they do not conform to the same interface. This means That if you're using Mojolicious to write a web application, but require an external library to interface with a remote API, because its interface will make your development easier, you cannot change it's use of LWP::UserAgent to Mojo::Useragent. Now you've added another dependency and complexity to your application.

Example: DBI

DBI is an example of a common interface to many different database drivers that do similar things, but underlyingly with different syntax. This allows you to use a common interface and ignore the differences in implementation between, say DBD::mysql and DBD::Pg.

How do I get there?

To begin, and as a general rule your interface should conform to style choices in the language you're using. Meaning that in Perl you should use $obj->foo_bar not $obj->getFooBar as it is the style most objects use.

Domain Driven Design

The first thing I suggest doing is design your initial interface using Domain Driven Design. Look at the common language used to describe the thing that you're building, and name your package, classes, methods, functions, after words from the common language. You're writing a new HTTP client? you probably have some concept of POST, so $client->post makes a lot of sense. If you're writing a billing system you may have some concept of Invoice->process. When doing Domain Driven Design your Interface should be easy to understand by an Expert in that domain (regardless of whether they are technically savvy ). Example if I told a Billing expert I was writing the code for Invoice->process they probably would have no idea what the internals meant, but they should easily understand the purpose and a general idea of what it actually does. (note: having an Invoice object might be bad, as an invoice is a request for payment on a Sale, and a receipt is a record of, therefore they are just views on a Sale, but that's a more complex notion)

Pure Fabrication

Unfortunately sometimes what you're creating has no real world equivalent (actually HTTP is an example that is now more of its own domain). So you're making it up as you go. In this case you need to create objects that are a Pure Fabrication. When creating interfaces for these I suggest looking to patterns, simple interfaces, and the interfaces for similar things in other projects, or languages. Use names that are as descriptive as you can get.

Existing Interfaces

You want to do this whenever there is an existing implementation that's not good enough, but has a decent interface. Perfect examples are DBI and LWP. They both have good interfaces, but there's a chance that the implementation isn't good enough (or you have need of a nonexistant driver).

An example with be AnyEvent::HTTP::LWP::UserAgent. If you're using AnyEvent you'll probably know you don't want LWP's blocking interface, but unfortunately the library you need to use uses LWP, what a dilemma. You could rewrite the library entirely to use AnyEvent::HTTP, but this will be both tedious and error prone. However, Anyevent::HTTP::LWP::UserAgent provides an LWP Interface, this means that you can simply substitute it in the library (hopefully the library made this easy by following the Inversion of Control Principle to be discussed in a future post).

Like AnyEvent::HTTP::LWP::UserAgent you may need to build a Facade interface that mimicks another interface. It would be better to start with this interface, but then again sometimes that's not ideal either.

Combinations

Sometimes you have to combine all of these strategies. Business::OnlinePayment::CyberSource (BOPC) and Business::CyberSource ( things I'm responsible for ) are good examples. Business::CyberSource was written because BOPC 2.x was no longer maintained and relied on a proprietary library which was not 64 bit compatible. I decided that I did not like the Business::OnlinePayment interface (and still don't to be honest ) and so set out to create a new one.

My first attempts was in retrospect focussed more on creating a perlish API than a Model driven API. In the long run this caused significant pain and resulted in some bad code. As of version 0.7.x (in TRIAL) Business::CyberSource's API is modeled after the remote API that CyberSource provides, and as such it has become much easier for me to provide access to new remote API features. Because I have continued work on ensuring that my Interface only relies on it's own interfaces it should now be trivial to replace any single piece of Business::CyberSources API. Don't want to use my request objects? you could simply pass an object that can serialize to a hashref that looks like what XML::Compile::SOAP expects. Any Expert at CyberSource should be able to read and understand my API (not tested), where they might not understand BOPC's. Unfortunately to get to this point I've had to break my interface several times.

Later due to new business concerns we had a need to conform to Business::OnlinePayments Interface, and so we rewrote BOPC to use Business::CyberSource as the backend. It does not provide access to all of the features, but it can be used in anything that knows how to use a Business::OnlinePayment API. I would have preferred to have this done sooner, but due to tuits and business constraints it was put off.

If you read the Source of either of these you will find a few fabrications, such as the use of Factories.

Interface as a Language Feature

Many languages support specifying the interface via a language feature. If your language supports this you should take advantage of it. Unfortunately Perl's simple can support really isn't enough, and Moose's Interface as Role support doesn't really work due to ordering issues. (I implemented the interface but unfortunately due to ordering my implementation is runtime and happens comes after the compiletime requirement ). I will say though that I believe it is more important to provide the actual calling convention in a dynamic language like Perl, than use of an actual interface. At least with Moose I feel that an interface is (generally) as concrete as the isa for the class, and so I don't bother checking them, they are an implementation detail.

Conclusion

Ultimately the goal is to create easy interfaces to understand, use, and properly reflective of the problem. By doing so you also make concrete implementations easy to update and reuse without breaking your clients.

Disqus