How Would You Design This Adapter? (Part 2)

Not all software programmers are destined to understand system design. Yet, it’s a necessary skill for building elegant, maintainable systems. I find that being able to craft well-designed software is not only internally satisfying but also a sought-after skill.

The only way to understand system design is by facing tricky software problems. This article describes a design problem containing a high-value learning opportunity. My follow-up article explains the surprising solution, which should become a key tool in your architectural toolbox.

 

OK, let’s get started.

Say, you’re the technical lead for your organisation’s mission-critical e-commerce system. One of the system’s core use cases enables customers to pay for their shopping cart at checkout. To that end, you have created a high-level business logic class, PayForShoppingCartUseCase which encapsulates the payment process in the following Pay() method:

  public class PayForShoppingCartUseCase
  {
     // ...

     public void Pay(ShoppingCart cart, SecurePaymentMethod paymentMethod)
     {
        Validation(cart, paymentMethod);
        var credentials = GetPaymentGatewayCredentials();
        PaymentGateway.Pay(cart, paymentMethod, credentials);

        // ...
     }
  }

The system must authenticate with the payment gateway before customers can pay for their shopping cart. Examining the code, it looks like helper method GetPaymentGatewayCredentials() retrieves the required authentication credentials from a datastore.

Currently, the system uses only a single payment gateway provided by ABC Corp. ABC provides an SDK to interact with their Payments API. This SDK contains an AbcClient we call to pay for the shopping cart.  

 

To avoid coupling our business logic, PayForShoppingCartUseCase, to the ABC SDK, we hide the ABC specifics from the business logic via an adapter, AbcPaymentGateway. PayForShoppingCartUseCase only ‘knows’ of interface IPaymentGateway, not of AbcPaymentGateway, which implements IPaymentGateway:

  public interface IPaymentGateway
  {
     void Pay(ShoppingCart cart, SecurePaymentMethod paymentMethod, Credentials credentials);
  }

Lastly, ABC’s simple payments solution requires authentication via username and password. We’ve created a separate class, Credentials, to hold this information:

  public class Credentials
  {
     public string Username { get; }
     public string Password { get; }

     public Credentials(string username, string password)
     {
        Username = username;
        Password = password;
     }
  }

Our implementation of the AbcPaymentGateway adapter class, which keeps the business logic class PayForShoppingCartUseCase ignorant of all details that are ABC specific, might look a bit like what we have below. I’d like you to focus on how the Credentials username and password are extracted and used as parameters in the call to the AbcClient:  

  public class AbcPaymentGateway : IPaymentGateway
  {
     public void Pay(ShoppingCart cart, SecurePaymentMethod paymentMethod, Credentials credentials)
     {
        // ...

        var username = credentials.Username;
        var password = credentials.Password;
 
        var abcResponse = AbcClient.InitiatePayment(cart.total, abcCardPayment,
                                username, password);

        // ...
     }
  }

PayForShoppingCartUseCase calls ABC Client via a ABC Payment Gateway adapter.

 

So far, so good.

XYZ Ltd also offers an online credit card payment solution. XYZ has an SDK for their payment service. We want to be able to use XYZ’s payment solution in our e-commerce application. To this end, we write a new implementation of IPaymentGateway for XYZ: XyzPaymentGateway

XYZ’s solution differs in a couple of aspects: Calls to XyzClient authenticate with a ClientId and ClientSecret, which are functionally equivalent to a username and password. Therefore we will treat them as such. However, and this may be a bigger problem, calls to XyzClient also need to be authenticated with an AccessToken. This AccessToken is specific to us as a client and never changes. 

Here is a provisional listing of XyzPaymentGateway:

  public class XyzPaymentGateway : IPaymentGateway
  {
     public void Pay(ShoppingCart cart, SecurePaymentMethod paymentMethod, Credentials credentials)
     {
        // ...

        var clientId = credentials.Username;
        var clientSecret = credentials.Password;
        var accessToken = ???;

        var xyzResponse = xyzClient.Pay(cart.total, xyzCardPayment,
                                        clientId, clientSecret, accessToken);

        // ...
     }
  }

PayForShoppingCartUseCase calls to XYZ Client also require an AccessToken which we didn’t have with ABC Client. How do we handle this?

 

Now, my question to you is simply this:

Consider the AccessToken required for authentication by XyzClient. In what way could that be conveyed to XyzClient? Keep in mind that AbcClient does not require this token for authentication. Is it necessary to modify theCredentials class, or can we get away without doing so? 

Please take 5 minutes to consider the problem before you look at the solution. Trust me, you’ll learn more.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply