Payment capabilities – outside or inside the service?

In my first payment blog post I would like to discuss the question about the organization of the capabilities within a payment service. I was provoked by the ‘ Recipe: Implement Capabilities’ within the Payment Cookbook. The recipe promotes that the service implements all the necessary capabilities like this:

public class MyPaymentServiceImpl implements PaymentService, Authorize, Capture
{
    @Override
    public Result<AuthorizationResult> authorize(PaymentContext context, Payable payable, Money authorizationAmount)
    {
        // implement
    }
 
    @Override
    public boolean canBeAuthorized(PaymentContext context)
    {
        return ...
    }
 
    @Override
    public Result<CaptureResult> capture(PaymentContext context, Money amount)
    {
        // implement
    }
}

My personal feeling is that this approach is quite wrong. Implementing so much interfaces within a single class reminds of God Object. Here are some problems with this approach.

Low cohesion and higher coupling

Mixing so much functionality leads to a huge class with different responsibilities. The complexity increases, maintainability is more difficult. The re-usability also decreases. What if we would like to reuse the Authorize capability? The approach also encourages coupling between the capabilities because developers will be tempted to share functionality between the methods.

Partner customization

Partners customize our code. This is a fact. And they do that even we try hard to cover most use cases. The problem is that they might want to customize a very particular capability, e.g. the Capture capability. This happens in real life, e.g. I know about a project that do not create an order when redirect after checkout is performed. Customizing a class which implements N interfaces is difficult. If we instead define each capability in a separate class (and even package) and use dependency injection in our service class then the partner can simply override a small capability. If we do it more elegantly the partners will even be able to add and remove capabilities.

Testing and TDD

There are very bad news for the fans of TDD and low level unit testing.Consider that we use some dependencies that facilitate our authorization and capture operations, like this:

public class MyPaymentServiceImpl implements PaymentService, Authorize, Capture
{
    @Inject private AuthorizeGadget authGadget;
    @Inject private AuthorizeCoolGadget authCoolGadget;
    @Inject private CaptureBoringGadget captureGadget;
}

The point is that if I decide to write an unit test that tests my Capture capability I would have to create mocks for all other dependencies that I do not need in my tests that are related with capture. Finally I will end up in bloated test cases.

Conclusion

I hope that this blog post will rise awareness about the organization of the capabilities and we may possibly rework the recipes and blue print cartridges.

Payment capabilities – outside or inside the service?
Tagged on: