How Would You Design This Adapter?

Recently I came across a captivating system design problem.

Imagine you had to design a class that converts calls between two interfaces—an adapter. Your adapter retrieves customer data from a specific CRM system sold by company XYZ. The XYZ CRM has an HTTP REST API that you can use to get data on customers. 

You’ve decided to name this adapter class XyzCustomerSource since it’ll be the source of customer data to high-level business logic callers. 

These callers will not instantiate and call XyzCustomerAdapter directly. You understand that knowledge of mechanisms, like XYZ CRM, in high-level business logic makes application architectures fragile and prone to ongoing maintenance problems. To avoid this fate, you’re hiding XyzCustomerSource behind interface ICustomerSource:

  public interface ICustomerSource
  {
     Task<Customer> GetCustomer(CustomerIdentifier id);
  }

CustomerIdentifier is a data transfer object holding various customer identifiers: 

  public class CustomerIdentifier
  {
     public Guid OrgId { get; set; }
     public Guid? CustomerId { get; set; }
     public string CustomerNumber { get; set; }
  }

The organisation identifier, OrgId, will always be present. And either the CustomerId or the CustomerNumber is also provided by callers to GetCustomer(). A null denotes the absence of a value for either CustomerId or CustomerNumber.

OK, let’s come back to the adapter, XyzCustomerSource. You have designed an initial version of XyzCustomerSource for a system—let’s call this System 1—where the CustomerId is available (but not the CustomerNumber) and passed in via the CustomerIdentifier:

  public class XyzCustomerSource : ICustomerSource
  {
     // ... 

     public async Task<Customer> GetCustomer(CustomerIdentifier id)
     {
        // Code to get a customer from the XYZ CRM REST API by OrgId and CustomerId. 
     }
  }

So far, so good. 

Now, you also have another system—let’s call this one System 2—that also requires customer data from the XYZ CRM. But there is one difference: System 2 only has access to the customer’s OrgId and CustomerNumber; you cannot use CustomerId. Luckily, XYZ CRM allows retrieval of customer data by CustomerNumber as well as CustomerId.  

You would like to reuse your XYZ CRM Adapter, XyzCustomerSource, between the two systems—in one, we have only CustomerId and in the other only CustomerNumber.

Here is my question to you: How would you change the design of XyzCustomerSource to handle single Customer retrieval by CustomerId or CustomerNumber? It would help if you provided some high-level pseudo-code for the crucial parts of XyzCustomerSource.

Note: You are allowed to make changes to ICustomerSource—if you must. 

I’ll publish my thoughts and preferred solution to this adapter design problem next time.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply