On Github brookingcharlie / cdc-intro
Consider Consumer-Driven Contracts as a Design Pattern.
Problem statement:
How can a web service API reflect its clients' needs while enabling evolution and avoiding breaking clients?
[1] Ian Robinson, Consumer-Driven Contracts, Service Design Patterns
How can we prevent breaking changes to services?
Option 1: service providers create APIs that attempt to address all current and future consumer needs. Option 2: service providers write tests based on an interpretation of how consumers might use the service. Option 3: consumers provide documentation to providers specifying how they expect to use the service. Option 4: something else?[1] Ian Robinson, Consumer-Driven Contracts, Service Design Patterns
The set of integration tests received from all existing consumers represents the service's obligations to its consumer base.
[1] Ian Robinson, Consumer-Driven Contracts, Service Design Patterns
Contracts codify the result of conversations - they don't replace them.
Consumer-Driven Contracts don't make sense everywhere.
They should be used when:
A service has several consumers, each with different needs. Service owners know who their consumers are. Client developers are able to communicate their expectations of the service's API to service owners.[1] Ian Robinson, Consumer-Driven Contracts, Service Design Patterns
"The similarly named Pact and Pacto are two new open-source tools which allow testing interactions between service providers and consumers in isolation against a contract." [1]
Service consumers define the requests they'll make and the responses they expect back. These expectations are used to run consumer tests against a mock service provider, recording interactions to a pact file.
Recorded interactions with the consumer are played back in the service provider tests to ensure the service provider actually does provide the response the consumer expects.
Spring Boot Microservices + Pact-JVM
git clone git@github.com:brookingcharlie/microservices-pact.git
# Build/test the consumer ./gradlew microservices-pact-consumer:test less microservices-pact-consumer/target/pacts/Foo_Consumer-Foo_Provider.json
# Build/test the provider ./gradlew microservices-pact-provider:assemble ./gradlew microservices-pact-provider:pactVerify
# Run the provider java -jar microservices-pact-provider/build/libs/microservices-pact-provider-0.0.1.jar # ... then in another window curl -v 'http://localhost:8080/foos/'
--- a/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Application.java +++ b/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Application.java @@ -26,4 +26,4 @@ public class Application { - @RequestMapping(value = "/foos", method = RequestMethod.GET) + @RequestMapping(value = "/foos", method = RequestMethod.GET, produces = "application/json;charset=ASCII") public ResponseEntity> foos() { return new ResponseEntity<>(Arrays.asList(new Foo(42), new Foo(100)), HttpStatus.OK); } --- a/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Foo.java +++ b/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Foo.java @@ -11,8 +11,8 @@ public class Foo { - public int getValue() { + public int getVal() { return value; } - public void setValue(int value) { + public void setVal(int value) { this.value = value; } }
patch -p1 -i break-provider.patch ./gradlew microservices-pact-provider:assemble microservices-pact-provider:pactVerify
--- a/microservices-pact-consumer/src/main/java/io/pivotal/microservices/pact/consumer/ConsumerPort.java +++ b/microservices-pact-consumer/src/main/java/io/pivotal/microservices/pact/consumer/ConsumerPort.java @@ -23,6 +23,6 @@ public class ConsumerPort { public List foos() { ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; - return restTemplate.exchange(url + "/foos", HttpMethod.GET, null, responseType).getBody(); + return restTemplate.exchange(url + "/foos", HttpMethod.POST, null, responseType).getBody(); } } --- a/microservices-pact-consumer/src/test/java/io/pivotal/microservices/pact/consumer/ConsumerPortTest.java +++ b/microservices-pact-consumer/src/test/java/io/pivotal/microservices/pact/consumer/ConsumerPortTest.java @@ -23,6 +23,6 @@ public class ConsumerPortTest { return builder.uponReceiving("a request for Foos") .path("/foos") - .method("GET") + .method("POST") .willRespondWith() .headers(headers)
patch -p1 -i break-consumer.patch ./gradlew microservices-pact-consumer:test microservices-pact-provider:pactVerify
--- a/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Foo.java +++ b/microservices-pact-provider/src/main/java/io/pivotal/microservices/pact/provider/Foo.java @@ -15,4 +15,8 @@ public class Foo { public void setValue(int value) { this.value = value; } + + public String getExtra() { + return "123"; + } }
patch -p1 -i evolve-provider.patch ./gradlew microservices-pact-provider:assemble microservices-pact-provider:pactVerify