@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
expectations
expectations
expectations
@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: ...
CONTR
ACT
CONTR
ACT
CONTR
ACT
@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
PACT
Karma
Test framework/Test runner
Browser (headless)
Mock/contract endpoint
App code (HTTP client,
model)Production code
exercises
invokes via socket
HTTP Api call
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
RakePACTproxy
Test framework/Test runner
Mock/contract endpoint
App code (service
provider)
Production code
invokes
pact json file
reads
validates
@andykelk | #SydPHP
Create the API provider<?phprequire '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();
@andykelk | #SydPHP
Use rake and pact provider proxy to testrequire '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 endend
@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-with
out-integration-tests-in-ruby/• http://techblog.realestate.com.au/enter-the-pact-matrix-or-how-to-decouple-t
he-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