Consumer Driven
Contracts
Remigiusz Dudek
Software Quality Engineer
Questions
• What problem we’re trying to solve?
• How the problem were solved so far?
• How we want to solve it with CDC?
• What other problems do we introduce?
End-to-end
testing in a
highly
distributed
environment
Deploy all services in
one environment
Automated or
Manual
Production
similarity
Environment
stability
Test analysis
Feedback loop
Environment as
bottleneck
Proper versions
Test isolation
Environment
Maintenance
Manual
Cost (low only in
small scales at
the beginning)
Deploy single service
with mocks
Production
similarity
Environment
Stability
Test analysis
Feedback loop
Environment as
bottleneck
Proper versions
Test isolation
Environment
maintenance
Cost
Testing in
general
https://martinfowler.com/articles/microservice-testing/
Testing in
general
https://martinfowler.com/articles/microservice-testing/
Consumer
Testing in
general
https://martinfowler.com/articles/microservice-testing/
Client
Producer
How to do it with CDC ?
• Prove that mocks are alike production
• If collaborator behaviour changes all parties should be informed
immediately
• Generate all the boilerplate code
tests
proving
contract
conformance
Stub
Boilerplate
Client
(Consumer)
Server
(Producer)
How?
1. Define problem
2. Define contract
3. Generate STUBs for Client (Consumer)
4. Use STUBs forClient’s tests
5. Generate tests for Server (Producer)
1. Define problem & prepare project
• https://start.spring.io/
• WEB
• Cloud ContractVerifier
• Cloud Contract Stub Runner
Trip Advisor
Hotel Advisor
Car rental
Advisor
Ticket
booking
Advisor
1. Define problem & prepare project
• https://start.spring.io/
• WEB
• Cloud ContractVerifier
• Cloud Contract Stub Runner
Trip Advisor
Hotel Advisor
Car rental
Advisor
Ticket
booking
Advisor
1. Contract dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-spec</artifactId>
</dependency>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>1.1.1.RELEASE</version>
<extensions>true</extensions>
</plugin>
1. Contract description
Trip Advisor Hotel Advisor
GET /hotels?location=Krakow
{
"availableHotels": [
{
"id": "779",
"name": "Sheraton",
"address": "Kraków, ul. Jana Nowaka 1”
},
{
"id": "892",
"name": "Novotel",
"address": "Kraków, ul. Długa 5”
}
]
}
1. Contract
• Ignoring contract
• Passing value from file (starting from version
1.2.0)
• Executing method available in base class
• Creating response basing on data from requestContract.make {
ignored()
}
response {
status 200
body (execute("commandName(${fromRequest().body('some/string/path')})"))
headers {
contentType(applicationJson())
}
}
2. Generate STUBs
• Contract verifier plugin
• Generate and run tests
• Create and install STUBs
$> mvn clean install -DskipTests
3. Use STUBs for client tests
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
</dependencies>@AutoConfigureStubRunner(
ids = {"prv.dudekre:hotels-advisor:0.0.1-SNAPSHOT:stubs:6565"},
stubsPerConsumer = true,
consumerName = „tripAdvisor”,
workOffline = true)
4. Generate tests for Server (Producer)
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>1.1.1.RELEASE</version>
<extensions>true</extensions>
<configuration>
<contractDependency>
<groupId>prv.dudekre.cdc</groupId>
<artifactId>Hotel-advisor-contracts</artifactId>
<classifier>stubs</classifier>
<version>0.0.1-SNAPSHOT</version>
</contractDependency>
<contractsPath>META-INF/prv.dudekre.cdc/Hotel-advisor-contracts</contractsPath>
<contractsWorkOffline>true</contractsWorkOffline>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*tripAdvisor.*</contractPackageRegex>
<baseClassFQN>prv.dudekre.cdc.contracts.HotelAdvisorBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
</configuration>
</plugin>
$> mvn help:describe -Dplugin=org.springframework.cloud:spring-cloud-contract-maven-plugin
Name: Spring Cloud Contract Maven Plugin
Description: Spring Cloud Contract Maven Plugin
Group Id: org.springframework.cloud
Artifact Id: spring-cloud-contract-maven-plugin
Version: 1.1.2.RELEASE
Goal Prefix: spring-cloud-contract
This plugin has 5 goals:
spring-cloud-contract:convert
Convert Spring Cloud Contract Verifier contracts into WireMock stubs mappings.
This goal allows you to generate `stubs-jar` or execute
`spring-cloud-contract:run` with generated WireMock mappings.
spring-cloud-contract:generateStubs
Picks the converted .json files and creates a jar. Requires convert to be
executed first
spring-cloud-contract:generateTests
From the provided directory with contracts generates the acceptance tests on
the producer side
spring-cloud-contract:help
Display help information on spring-cloud-contract-maven-plugin.
Call mvn spring-cloud-contract:help -Ddetail=true -Dgoal=<goal-name> to
display parameter details.
spring-cloud-contract:run
Testing in
general
https://martinfowler.com/articles/microservice-testing/
Client
Producer
4. Generate tests for Server (Producer)
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {ContractTestConfiguration.class})
public abstract class HotelsBase {
@Autowired HotelAdvisorService hotelAdvisorService;
@Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(
new HotelAdvisorController(hotelAdvisorService));
}
}
APIVersioning
• Semantic versioning
• Keep contracts for each version of API
• Consider maven classifiers to mark production/dev versions (mind that it
doesn’t match the original idea behind classifier concept)
• Keep contracts in separate module/repository (different release cycle)
Defer breaking change
• Tolerant reader pattern
• Expand and contract pattern (3 steps to unbreake API change)
• 6 steps to unbreak DB schema change in continous deployment
environment (if you’re not doing continuous deployment you can simplify
the algorithm and have only 5 steps)
Expand and
Contract
From Sam Newman’s book „Building Microservices”
5 steps algorithm
• 0. Service (v1.0) using old data
structure
• 1. Add new data structure (1st
DB change) incompatible with
the old one (omitt constrainsts)
5 steps algorithm
• 2. Deploy new version (v1.1) of
your service reading from old
structure and writing to old and
new
5 steps algorithm
• 3. Migrate all old data to new
structure (2nd DB change) and
add constraints to new data
structure
5 steps algorithm
• 4. Release new version of your
service (v1.2) reading from new
and writing to new data
structure
5 steps algorithm
• 4. Clean old data structures (3rd
DB change)
2 service releases
3 DB changes
6 steps algorithm
• 0. Service (v1.0) using old data
structure
• 1. Deploy new version of your
service (v1.1) marking record
that it created/updated (not
needed if not doing continuous
deployment)
6 steps algorithm
• 2. Add new data structure (1st
DB schema change)
incompatible with the old one
(omitt constrainsts)
6 steps algorithm
• 3. Deploy new version of your service
(1.2) with quite complex (simple if not
doing continuous deployment) data
creation/updating logic:
• Create new data in old and new
structure
• If old data is not marked and new data
does not exist, read from old and write
to old and new
• If old data is not marked and new data
exists, read from new and write to old
and new
• If old data is marked, read from old and
write to old and new and remove marker
6 steps algorithm
• 4. Migrate old data in case when
new data does not exist or old
data is marked and add
constraints to new data
structure
6 steps algorithm
• 5. Deploy new version (1.3) of
your service reading from new
and writing to new data
structure
6 steps algorithm
• 6. Remove old data structure
CDC Advanced
• State-based behaviour (http://wiremock.org/docs/stateful-behaviour/)

[TestWarez 2017] Behavior Driven Development in a complex environment - Consumer Driven Contracts

  • 1.
  • 2.
    Questions • What problemwe’re trying to solve? • How the problem were solved so far? • How we want to solve it with CDC? • What other problems do we introduce?
  • 3.
  • 4.
    Deploy all servicesin one environment Automated or Manual Production similarity Environment stability Test analysis Feedback loop Environment as bottleneck Proper versions Test isolation Environment Maintenance Manual Cost (low only in small scales at the beginning)
  • 5.
    Deploy single service withmocks Production similarity Environment Stability Test analysis Feedback loop Environment as bottleneck Proper versions Test isolation Environment maintenance Cost
  • 6.
  • 7.
  • 8.
  • 9.
    How to doit with CDC ? • Prove that mocks are alike production • If collaborator behaviour changes all parties should be informed immediately • Generate all the boilerplate code
  • 10.
  • 11.
    How? 1. Define problem 2.Define contract 3. Generate STUBs for Client (Consumer) 4. Use STUBs forClient’s tests 5. Generate tests for Server (Producer)
  • 12.
    1. Define problem& prepare project • https://start.spring.io/ • WEB • Cloud ContractVerifier • Cloud Contract Stub Runner Trip Advisor Hotel Advisor Car rental Advisor Ticket booking Advisor
  • 13.
    1. Define problem& prepare project • https://start.spring.io/ • WEB • Cloud ContractVerifier • Cloud Contract Stub Runner Trip Advisor Hotel Advisor Car rental Advisor Ticket booking Advisor
  • 14.
  • 15.
    1. Contract description TripAdvisor Hotel Advisor GET /hotels?location=Krakow { "availableHotels": [ { "id": "779", "name": "Sheraton", "address": "Kraków, ul. Jana Nowaka 1” }, { "id": "892", "name": "Novotel", "address": "Kraków, ul. Długa 5” } ] }
  • 16.
    1. Contract • Ignoringcontract • Passing value from file (starting from version 1.2.0) • Executing method available in base class • Creating response basing on data from requestContract.make { ignored() } response { status 200 body (execute("commandName(${fromRequest().body('some/string/path')})")) headers { contentType(applicationJson()) } }
  • 17.
    2. Generate STUBs •Contract verifier plugin • Generate and run tests • Create and install STUBs $> mvn clean install -DskipTests
  • 18.
    3. Use STUBsfor client tests <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope> </dependency> </dependencies>@AutoConfigureStubRunner( ids = {"prv.dudekre:hotels-advisor:0.0.1-SNAPSHOT:stubs:6565"}, stubsPerConsumer = true, consumerName = „tripAdvisor”, workOffline = true)
  • 19.
    4. Generate testsfor Server (Producer) <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>1.1.1.RELEASE</version> <extensions>true</extensions> <configuration> <contractDependency> <groupId>prv.dudekre.cdc</groupId> <artifactId>Hotel-advisor-contracts</artifactId> <classifier>stubs</classifier> <version>0.0.1-SNAPSHOT</version> </contractDependency> <contractsPath>META-INF/prv.dudekre.cdc/Hotel-advisor-contracts</contractsPath> <contractsWorkOffline>true</contractsWorkOffline> <baseClassMappings> <baseClassMapping> <contractPackageRegex>.*tripAdvisor.*</contractPackageRegex> <baseClassFQN>prv.dudekre.cdc.contracts.HotelAdvisorBase</baseClassFQN> </baseClassMapping> </baseClassMappings> </configuration> </plugin>
  • 20.
    $> mvn help:describe-Dplugin=org.springframework.cloud:spring-cloud-contract-maven-plugin Name: Spring Cloud Contract Maven Plugin Description: Spring Cloud Contract Maven Plugin Group Id: org.springframework.cloud Artifact Id: spring-cloud-contract-maven-plugin Version: 1.1.2.RELEASE Goal Prefix: spring-cloud-contract This plugin has 5 goals: spring-cloud-contract:convert Convert Spring Cloud Contract Verifier contracts into WireMock stubs mappings. This goal allows you to generate `stubs-jar` or execute `spring-cloud-contract:run` with generated WireMock mappings. spring-cloud-contract:generateStubs Picks the converted .json files and creates a jar. Requires convert to be executed first spring-cloud-contract:generateTests From the provided directory with contracts generates the acceptance tests on the producer side spring-cloud-contract:help Display help information on spring-cloud-contract-maven-plugin. Call mvn spring-cloud-contract:help -Ddetail=true -Dgoal=<goal-name> to display parameter details. spring-cloud-contract:run
  • 21.
  • 22.
    4. Generate testsfor Server (Producer) @RunWith(SpringRunner.class) @ContextConfiguration(classes = {ContractTestConfiguration.class}) public abstract class HotelsBase { @Autowired HotelAdvisorService hotelAdvisorService; @Before public void setup() { RestAssuredMockMvc.standaloneSetup( new HotelAdvisorController(hotelAdvisorService)); } }
  • 23.
    APIVersioning • Semantic versioning •Keep contracts for each version of API • Consider maven classifiers to mark production/dev versions (mind that it doesn’t match the original idea behind classifier concept) • Keep contracts in separate module/repository (different release cycle)
  • 24.
    Defer breaking change •Tolerant reader pattern • Expand and contract pattern (3 steps to unbreake API change) • 6 steps to unbreak DB schema change in continous deployment environment (if you’re not doing continuous deployment you can simplify the algorithm and have only 5 steps)
  • 25.
    Expand and Contract From SamNewman’s book „Building Microservices”
  • 26.
    5 steps algorithm •0. Service (v1.0) using old data structure • 1. Add new data structure (1st DB change) incompatible with the old one (omitt constrainsts)
  • 27.
    5 steps algorithm •2. Deploy new version (v1.1) of your service reading from old structure and writing to old and new
  • 28.
    5 steps algorithm •3. Migrate all old data to new structure (2nd DB change) and add constraints to new data structure
  • 29.
    5 steps algorithm •4. Release new version of your service (v1.2) reading from new and writing to new data structure
  • 30.
    5 steps algorithm •4. Clean old data structures (3rd DB change) 2 service releases 3 DB changes
  • 31.
    6 steps algorithm •0. Service (v1.0) using old data structure • 1. Deploy new version of your service (v1.1) marking record that it created/updated (not needed if not doing continuous deployment)
  • 32.
    6 steps algorithm •2. Add new data structure (1st DB schema change) incompatible with the old one (omitt constrainsts)
  • 33.
    6 steps algorithm •3. Deploy new version of your service (1.2) with quite complex (simple if not doing continuous deployment) data creation/updating logic: • Create new data in old and new structure • If old data is not marked and new data does not exist, read from old and write to old and new • If old data is not marked and new data exists, read from new and write to old and new • If old data is marked, read from old and write to old and new and remove marker
  • 34.
    6 steps algorithm •4. Migrate old data in case when new data does not exist or old data is marked and add constraints to new data structure
  • 35.
    6 steps algorithm •5. Deploy new version (1.3) of your service reading from new and writing to new data structure
  • 36.
    6 steps algorithm •6. Remove old data structure
  • 37.
    CDC Advanced • State-basedbehaviour (http://wiremock.org/docs/stateful-behaviour/)

Editor's Notes

  • #5 Environment stability - usually machines in such test environment are much weaker then in production or much higher burden is put on them - since we have hundreds of services, each is usually deployed independently hence you never know what is trully currently deployed on environment Proper versions - how to ensure that your service is tested against proper versions of other services (the ones that are deployed on prod) in such dynamic environment Environment as a bottleneck - Proper versions – one would have to deploy newest version of his service and ask to freeze the environment for the time of tests - test isolation – one would have to ask other teams no to perform their tests in order not to interfere Test analysis - if a test fails, you don’t really know which service failed, finding root cause is a nightmare
  • #6 Environment stability - usually machines in such test environment are much weaker then in production or much higher burden is put on them - since we have hundreds of services, each is usually deployed independently hence you never know what is trully currently deployed on environment Proper versions - how to ensure that your service is tested against proper versions of other services (the ones that are deployed on prod) in such dynamic environment Environment as a bottleneck - Proper versions – one would have to deploy newest version of his service and ask to freeze the environment for the time of tests - test isolation – one would have to ask other teams no to perform their tests in order not to interfere Test analysis - if a test fails, you don’t really know which service failed, finding root cause is a nightmare
  • #19 groupId:artifactId:version:classifier:port If you don’t provide the port then a random one will be picked If you don’t provide the classifier then the default one will be taken. If you don’t provide the version then the + will be passed and the latest one will be downloaded
  • #21 Configuration used to find a base class for all generated tests. The goal of the base class is to run the server under test.
  • #24 Classifier - The classifier allows to distinguish artifacts that were built from the same POM but differ in their content