API Client Design

When you extensively work with certain APIs, like Shopify’s for example, you will end up with bunch of functions that map to API’s endpoints. One of the approaches I have seen so far is to create an API class ShopifyApi and make those functions class methods. So it looks something like the figure below.

I see several problems with this approach. First of all it is not really object oriented design. A bunch of functions wrapped in a class are still a bunch of functions. Second, it violates Single Responsibility principle. ShopifyApi class is obviously trying to do a lot of things. Changes in different areas will require you to change this class. Thirdly, this class is not very testable. It will be hard to test it without hitting an actual API endpoint.

Let’s make some changes to this class. In PHP we have several ways of sending HTTP request. For example, Guzzle library, Curl PHP extension, or just simple streams. A good refactor will be to pass a transport into ShopifyApi class instead of having transport inside. We can leverage Composition design principle here.

By coding to interface we can choose different implementations of the transport and even swap them at run time. Personally I also use couple helper classes to pass data in and out of the transport: Request and Response.

Now we can use the interface to test our code without hitting an actual API endpoint. We just need to create a fake transport class that implements Transport interface and returns a dummy data. We can pass this class to ShopifyApi when running our tests. In this refactor we use Dependency Inversion design principle

Now let’s split up our ShopifyApi “super class”. We can take each responsibility this class handles and split them out into their own classes

This refactor takes advantage of OOP’s Inheritance.  Depending on responsibilities, parent class can be either abstract or concrete. Child classes can be as small as containing only one method. Don’t make this fact keep you from refactoring: “Well, if we extract Fulfillment class, it will only have one method, so it is wasteful allocating the whole class to it”. It may be true now, but later on Shopify will change the way they want you to send fulfillments. For example recently Shopify added locations. Now your Fulfillment class has to figure out what location to use when sending a fulfillment. The good thing about it is that you only have to change Fulfillment class. Your client code still uses the same interface and does not know about the changes. You can see how Single Responsibility and Encapsulation pay off in this case.

In multi tenant applications you may have to use different API credentials for different clients.  In this case you can create a some kind of credentials provider.  So your design may start looking more like this:

Before you start implementing credentials provider, first think if you really need it. Personally I am against unnecessary complexity and use YAGNI.  “You aren’t gonna need it” is a principle that states functionality should not be added until necessary.  If your app does not support multi tenancy chances are it won’t do it in the future.  Multi tenancy requires a different design approach.  If your app uses only one set of credentials to connect to Shopify API, keep it simple and don’t develop unnecessary structure to provide credentials to the API client. YAGNI.

As you can see, the design can become quite involved.  I suggest using common sense when creating an API client.  If your app only needs to sync products from Shopify, the suggested design complexity may not be justified.  But if there is a lot of stuff going on and your app uses a lot of endpoints, it makes more sense to have a testable design that can accommodate future changes without requiring much work.  The more time you invest in building a software, the longer you expect it to live.  This means you should be able to easily adapt it to changing requirements and make necessary updates fast.

Share this article

Similar Posts

  • |

    Debugging Webhooks

    A webhook is an HTTP callback, that occurs when something happens (a resource changes its state). Webhooks provide a way to build event-driven apps, because you can be notified about changes. Since webhooks require a publicly accessible URL to function they can be hard to test from your local machine.  There are three main problems…

    Share this article
  • |

    Testing API Clients

    Since an API client is a boundary between your code and the outside world you should write as little code as possible to implement it. The client should strictly do its job by sending a request and returning a response. That is why we don’t really test the clients themselves but rather the code that processes the response from the client.

    Share this article
  • Upload to FTP with PHP

    $fp = fopen(‘https://www.example.com/pdfdoc’, ‘r’); $user = “sammy”; $pass = “password”; $ftp_server = “192.168.10.10”; //should be wrapped in try catch to properly handle errors $ftp_conn = ftp_ssl_connect($ftp_server); $login = ftp_login($ftp_conn, $user, $pass); ftp_chdir($ftp_conn, ‘path/to/folder’); //can also use ftp_pwd ftp_pasv($ftp_conn, true); //passive mode ftp_fput($ftp_conn, “mydocument.pdf”, $fp, FTP_BINARY); fclose($fp); ftp_close($ftp_conn); Above code can be used to upload a…

    Share this article
  • Complex Eloquent Query

    A query below selects products that need to be updated in a remote application. It takes quantities of products in host application that are connected to source products application. On top of that it looks at all the orders that are in “Pending” status and reserves quantities for those products. SELECT products.quantity_available, connector_products.stock_id, products.id, connector_products.sku,…

    Share this article