Andy Kelk

Digital Technology Leader


Consumer-driven contracts with Pact and PHP

This post is a paraphrasing of a talk I gave at the Sydney PHP group.


First up, let’s talk about architectures that rely on services. Whether you call them SOA or you’ve jumped feet first into Microservices, there’s common challenges you need to solve. One of the biggest is coupling and dependency between service providers and consumers.

Here’s a typical architecture where a provider has a number of different consumers.

Slide02

 

You’ll notice that each of the consumers have expectations about the service provider. What’s important to note is that these expectations differ from one consumer to the next. For instance, if our provider were delivering content to the consumers, one consumer may be taking all of the details of a piece of content (its title, its media assets, its categories) while another may only be taking aggregate data about the categories. The difficulty in maintaining the provider is knowing which consumers rely on which bits of the service you provide; because of that, changing the provider’s interface or implementation becomes a burden.

Enter the Consumer-Driven Contracts pattern:

Slide03

The pattern was first introduced by Ian Robinson in an article published in 2006. The core idea is that each consumer creates a contract with the provider based on what it expects. The provider aggregates those contracts and uses those as a mechanism for validating that the service it provides meets the consumers’ expectations. The aggregation could be as simple as a spreadsheet or document listing the consumers and their expectations. However, it’s possible to use the contracts as the basis for automated tests.

Slide04

Pact is an implementation of automated testing based on consumer-driven contracts. It was first developed by DiUS during their work at REA Group as a ruby gem.

It has since also been implemented for the JVM and in .Net. It’s possible to run pact for a JavaScript consumer although that requires that Ruby be installed. There is also a functioning consumer implementation for Swift (for iOS development). Pact is run in a two stage process – on the consumer as a mock service which generates a pact file and then on the provider to verify that the provider meets the consumer-driven contract defined in the pact file.

Slide06

If you’re paying attention, you will have noticed that this post is about using Pact in PHP and there’s no implementation in PHP. The good news is that we can work around that by using the Pact provider proxy which uses the ruby gem to verify a provider using HTTP.

So let’s start by creating a consumer implementation using JavaScript. This is based on the instructions from the project’s github page. We’ll be using Karma/Jasmine as the testing harness; that will invoke PhantomJS as a browser and exercise our production code.

Slide08

The first thing we’ll do is to setup the pact mock service.

beforeEach(function() {
  client = ZooClient.createClient('http://localhost:1234');

  alligatorProvider = Pact.mockService({
    consumer: 'Alligator Consumer',
    provider: 'Alligator Provider',
    port: 1234,
    done: function (error) {
      expect(error).toBe(null);
    }
  });
});

Here, ZooClient is from our production code and creates an XmlHttpRequest client for us. We pass in a parameter to our client to point it to our pact mock service instead of the real service.

We then tell our mock service to listen on a particular port, give it a name for the consumer and provider, and pass it a callback function to execute on completion. This function will fail the test if anything has gone wrong during our mock interaction.

Then we write the actual test code.

it("should return an alligator", function(done) {
  alligatorProvider
    .given("an alligator with the name Mary exists")
    .uponReceiving("a request for an alligator")
    .withRequest("get", "/alligators/Mary", {
      "Accept": "application/json"
    }).willRespondWith(200, {
      "Content-Type": "application/json"
    }, {
      "name": "Mary"
    });

  alligatorProvider.run(done, function(runComplete) {
    expect(client.getAlligatorByName("Mary")).toEqual(new Alligator("Mary"));
    runComplete();
  });
});

We set up state in the provider using the ‘given’ method (more on this later). We then go on to describe the interaction we expect – which URL to call, what headers the request will have, the response code, headers and body we expect to receive back.

The last thing we do is to verify that, when the client code is called with the mock service, we get back the object (an Alligator) that we expected.

We’ve now unit tested (some of) our client code and we have a pact file that describes the interaction our client code expects to have with the service provider. It’s simply a json file:

{
  "consumer": {
    "name": "Alligator Consumer"
  },
  "provider": {
    "name": "Alligator Provider"
  },
  "interactions": [
    {
      "description": "a request for an alligator",
      "provider_state": "an alligator with the name Mary exists",
      "request": {
        "method": "get",
        "path": "/alligators/Mary",
        "headers": {
          "Accept": "application/json"
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "body": {
          "name": "Mary"
        }
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.0.0"
  }
}

Let’s look at how to use this to verify that our PHP provider is working. The pact provider proxy is a ruby gem so we’ll be using Rake as our task runner to execute it, pact will read the json file we just created and will then verify our provider API.

Slide12

To setup our PHP API we’re going to use Slim. Here’s our very simple provider code:

<?php require 'vendor/autoload.php';
  $app = new \Slim\Slim(); $app->get('/alligators/:name', function ($name) use ($app) {
  $app->response->setStatus(200);
  $app->response()->headers->set('Content-Type', 'application/json');
  echo json_encode(array('name' => $name));
});
$app->run();

All we’ll be doing is echoing back the name that we receive in the API call. The Rake task we’ve built points to the json file we created from our consumer. For this demo, I’ve just manually copied that file from the consumer to the provider project. In reality you’ll want something more robust than that. We also tell Pact where to find our API (running on port 8000).

require 'pact/provider/proxy/tasks'

Pact::ProxyVerificationTask.new :alligator do | task |
 task.pact_url './spec/pacts/alligator_consumer-alligator_provider.json', \
   :pact_helper => './spec/support/alligator_pact_helper' 
 task.provider_base_url 'http://localhost:8000'
end

You’ll notice that there’s also a pact helper specified. This is the code for the helper:

Pact.provider_states_for "Alligator Consumer" do
  provider_state "an alligator with the name Mary exists" do
    set_up do
      # Set-up the provider state (e.g. create fixture) here
    end
  end
end

The helper is there to set up the state of my provider before I execute the verification. Usually this would be achieved by inserting a fixture into a database or making sure that some other tasks had executed to make sure I had a known state. Obviously, in my simple example there’s no state (and, in fact, I could have not provided a “given” clause in my consumer test as the provider is stateless).

This illustrates one of the key downsides of using the provider proxy over a native implementation: if I want to set up state, I need to have some way for Pact to do that. This might mean that you need to open your internals to some code external to your application; it also means you’ll likely need to reimplement some of the data logic.

Alternatively, you could expose an endpoint in your PHP code to do the setup for your provider and call that from Pact. That way you encapsulate your data access inside your application at the expense of exposing a way of invoking it. Either way, there’s overhead in not using a native verification tool.

In our simple example, that’s all that’s needed to verify that our API is working. By calling rake, I can now be sure that my provider is returning what the consumer expects. If I make a change such as using firstName/lastName instead of name as data elements, my Pact verification will fail, I can pinpoint which consumers are going to be impacted and plan for the change with them.

Next Steps

So what’s next? Personally, I think there’s room for a PHP implementation of Pact. As well as allowing provider states to be handled natively, it would also mean not having to install Ruby in your development environment if you’re a PHP shop.

The other step I’d take would be to use the Pact broker as a way to maintain pact files between consumers and providers. As well as removing the need to manually move files around, it can also generate documentation and diagrams, it can tag pacts as a particular version and it provides webhooks to trigger builds when a new pact is published.

Further Reading

If you’re interested in finding our more, here’s some further reading:

The code I used for the demo in this post is available at https://github.com/mopoke/pact-php-demo and the slides are on slideshare.


Leave a Reply

Your email address will not be published. Required fields are marked *

*
*


Archives