Implement a dependance to an external service API with Laravel

Not all APIs are badly designed, I heard. Oh, and also: unicorns are real.

Philippe Lonchampt
code16
6 min readMay 26, 2018

--

A Virgin with a Unicorn, by Domenichino — wikipedia

These days, many websites or apps we build are linked to external services. For some of them Laravel does a great job to handle them: transactional emails, accessing remote servers via FTP, … and for some others, there is already very good packages (Laravel Cashier for Stripe for instance). But in many cases, we have to build our own implementation, and it can be tricky, mainly in my experience for two reasons:

  • The external service’s API can be badly designed, or in a shape that does not fit our needs. Okay, this is almost always the case. Okay, I remove the “almost”.
  • The adherence with this external service, on which we have no control, can be a huge issue: we have to handle downtimes, API updates, and worse of all: unplanned API updates.

In a recent project we developed, at Code 16, we had to connect with some external rental service, created years ago, with a quite disturbing XML based SOAP API built as time goes by without any standard (at least, nothing we could find out), nor any documentation (but this is not the subject here, I’m just looking for compassion). Let’s name here this external service “LS”.

How to connect to the service

Here’s how we tackled this, with the idea to limit adherence between our domain code and LS. First we built a general Client, in a dedicated namespace:

Note that all code bits in this article are very simplified version of actual production code, for clarity.

This LsClient is in charge of sending request and receiving responses from the external service with the help of PHP’s standard SoapClient class.

Of course, we don’t want our domain code to directly use this `call()` method, so our first layer is to add one method per real usage in this client. For instance:

Simple enough, now we can call `$lsClient->getProductPrices(…)`, which is a way more readable code. But, among other issues, a problem is to handle our business rules and validation here: imagine you want to create a new quote, what is the required data, and what happens if the given data is invalid? And beyond that, how can we format the data returned?

Action classes

Let’s build another layer, closer to the domain: Action classes, one per service. Here’s how we could write our CreateQuoteAction:

Since `Order` and `Quote` are domain’s models, this class allows us to have a good abstraction for our LsClient. All validation, formatting, … can be coded in this class. So maybe in our domain codebase we have a Job like this one:

How to handle errors

Of course, because this is an external service, we will get errors: applicative ones, plus network, down server, … So first thing: we have to raise understandable exceptions, and our Action class is the perfect place for it:

We handle Soap exceptions as well as LS errors, converting them in domain exceptions to be able to catch them in our domain code. Note that a Soap exception could occur in any action, and that some LS errors are also generic. Let’s build a base Action class to handle this once for good:

And simplify our CreateQuote class:

The newly created LsAction class is a good spot to add logging, too, which is very important to understand any real life production issue, even more in an external service case. I will not enter into this level of detail, but be sure to check Laravel 5.6 new logging features to keep those logs separate to your actual app logs (both config and log files).

One final step: in many cases (all of them, in our project), domain jobs will be executed in queues, meaning raised exceptions have to be catched somewhere. Laravel has a pretty clever way to do this in jobs:

Now in a failure case we ensure that our Order state is updated, and that some admin is warned. All this without crashing on the customer side, thanks to queues.

How to test our code

Last but not least: how can we test this?

First thing: what should we test? Answer is clear to me, here: our code, and just our code*. In this particular case, the border is between Actions classes and the LsClient class. The latter should be unit tested, once for all, but Actions classes should be part of our integration tests (I mostly do integration tests anyway).

* To be clear, when I can I hit external services in tests, and I love it. But in many cases, and this one in particular, this is not an option: we can’t keep a stable test environment, nor reset most of our test data via API

To me, the easiest way to do that is to use Fakes. Our LsClient should implement an interface:

… which should be the one injected in Action classes:

… with binding declared in AppServiceProvider:

Now we can do this in tests:

In each test, or in each test class, we can define a fake implementation returning what we need for this particular test, and then checking against exceptions, data in database, … All our code being touched.

What now?

Well, we could talk about log management, look more deeply at the abstract Action class code since it can be enhanced a lot, talk about shitty APIs, but truth is I think this is enough for now, and more importantly it is a sunny day: let’s cut here. I hope this post was useful to some of you.

--

--

Philippe Lonchampt
code16

Developer and founder of Code 16, maintainer of Laravel Sharp.