@andykelk | #SydPHP
Consumer-driven
Contracts with Pact and
PHP
@andykelk | #SydPHP
provider
consumer 1
consumer 2
consumer 3
{
status: “ok”,
count: 20,
items: [
{
id: 10000,
name: ...
Providers and consumers
@andykelk | #SydPHP
Consumer-driven contracts
• In a service oriented architecture, a service provider
typically has many consumers.
• Each of those consumers has expectations about the
service.
• Over time, consumer expectations change and the
provider must evolve to meet them.
• Standard approaches to this introduce
interdependence into the relationship.
• The Consumer-Driven Contracts pattern solves that.
@andykelk | #SydPHP
Consumer-driven contracts
provider
consumer 1
consumer 2
consumer 3
{
status: “ok”,
count: 20,
items: [
{
id: 10000,
name: ...
@andykelk | #SydPHP
Pact
• Ruby gem which implements automated testing for
consumer-driven contracts
• There are also implementations for
• .Net (Provider, Consumer)
• JVM (Provider, Consumer)
• JavaScript (Provider, Consumer)
• Swift (Consumer)
@andykelk | #SydPHP
Pact
https://github.com/realestate-com-au/pact
@andykelk | #SydPHP
Pact
• BUT… no PHP implementation 😞
• Luckily we can use the Provider Proxy to help us test a
PHP service provider
@andykelk | #SydPHP
JavaScript Consumer
Jasmine
PhantomJS
PAC
T
Karma
Test framework/
Test runner
Browser
(headless)
Mock/contract
endpoint
App code
(HTTP client,
model)
Production code
exercise
s
invokes via
socket
creates
pact json file
@andykelk | #SydPHP
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);
}
});
});
Setting up a JavaScript consumer
@andykelk | #SydPHP
Setting up a JavaScript consumer
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();
});
});
@andykelk | #SydPHP
Now we have a pact
{
"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" }
}
@andykelk | #SydPHP
PHP Provider
Rake
PAC
T
prox
y
Test framework/
Test runner
Mock/contract
endpoint
App code
(service
provider)
Production code
invokes
pact json file
reads
@andykelk | #SydPHP
Create the API provider
<?php
require 'vendor/autoload.php';
$app = new SlimSlim();
$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();
@andykelk | #SydPHP
Use rake and pact provider proxy to test
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
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
@andykelk | #SydPHP
Demo Time!
@andykelk | #SydPHP
What’s next?
• PHP implementation of the pact specification
• Use pact broker to share pact files and provide:
• Auto-generated documentation
• Dynamically generated network diagrams
• The ability to tag a pact (ie. "prod") so a provider
can verify itself against a fixed version of a pact to
ensure backwards compatibility.
• Webhooks to trigger provider builds when a pact is
published.
@andykelk | #SydPHP
Questions?
• Further reading:
• http://martinfowler.com/articles/consumerDrivenContracts.html
• https://github.com/realestate-com-au/pact
• http://techblog.realestate.com.au/testing-interactions-with-web-services-
without-integration-tests-in-ruby/
• http://techblog.realestate.com.au/enter-the-pact-matrix-or-how-to-
decouple-the-release-cycles-of-your-microservices/
• Code from the demo:
• https://github.com/mopoke/pact-php-demo
• Relive this talk in blog form:
• http://www.andykelk.net/tech/consumer-driven-contracts-with-pact-and-
php

Consumer-driven contracts with Pact and PHP

  • 1.
  • 2.
    @andykelk | #SydPHP provider consumer1 consumer 2 consumer 3 { status: “ok”, count: 20, items: [ { id: 10000, name: ... Providers and consumers
  • 3.
    @andykelk | #SydPHP Consumer-drivencontracts • In a service oriented architecture, a service provider typically has many consumers. • Each of those consumers has expectations about the service. • Over time, consumer expectations change and the provider must evolve to meet them. • Standard approaches to this introduce interdependence into the relationship. • The Consumer-Driven Contracts pattern solves that.
  • 4.
    @andykelk | #SydPHP Consumer-drivencontracts provider consumer 1 consumer 2 consumer 3 { status: “ok”, count: 20, items: [ { id: 10000, name: ...
  • 5.
    @andykelk | #SydPHP Pact •Ruby gem which implements automated testing for consumer-driven contracts • There are also implementations for • .Net (Provider, Consumer) • JVM (Provider, Consumer) • JavaScript (Provider, Consumer) • Swift (Consumer)
  • 6.
  • 7.
    @andykelk | #SydPHP Pact •BUT… no PHP implementation 😞 • Luckily we can use the Provider Proxy to help us test a PHP service provider
  • 8.
    @andykelk | #SydPHP JavaScriptConsumer Jasmine PhantomJS PAC T Karma Test framework/ Test runner Browser (headless) Mock/contract endpoint App code (HTTP client, model) Production code exercise s invokes via socket creates pact json file
  • 9.
    @andykelk | #SydPHP 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); } }); }); Setting up a JavaScript consumer
  • 10.
    @andykelk | #SydPHP Settingup a JavaScript consumer 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(); }); });
  • 11.
    @andykelk | #SydPHP Nowwe have a pact { "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" } }
  • 12.
    @andykelk | #SydPHP PHPProvider Rake PAC T prox y Test framework/ Test runner Mock/contract endpoint App code (service provider) Production code invokes pact json file reads
  • 13.
    @andykelk | #SydPHP Createthe API provider <?php require 'vendor/autoload.php'; $app = new SlimSlim(); $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();
  • 14.
    @andykelk | #SydPHP Userake and pact provider proxy to test 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 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
  • 15.
  • 16.
    @andykelk | #SydPHP What’snext? • PHP implementation of the pact specification • Use pact broker to share pact files and provide: • Auto-generated documentation • Dynamically generated network diagrams • The ability to tag a pact (ie. "prod") so a provider can verify itself against a fixed version of a pact to ensure backwards compatibility. • Webhooks to trigger provider builds when a pact is published.
  • 17.
    @andykelk | #SydPHP Questions? •Further reading: • http://martinfowler.com/articles/consumerDrivenContracts.html • https://github.com/realestate-com-au/pact • http://techblog.realestate.com.au/testing-interactions-with-web-services- without-integration-tests-in-ruby/ • http://techblog.realestate.com.au/enter-the-pact-matrix-or-how-to- decouple-the-release-cycles-of-your-microservices/ • Code from the demo: • https://github.com/mopoke/pact-php-demo • Relive this talk in blog form: • http://www.andykelk.net/tech/consumer-driven-contracts-with-pact-and- php