In order to complete this lab, you will need the following:
- A Pivotal Web Services account
- Spring Tool Suite or your preferred Java/Spring IDE
- Lastes version of Maven
- Latest version of Git client
- Latest version of Docker and Docker Compose
- Latest version of Concourse's fly command-line tool
Dev teams frequently need to make schema changes and/or functionality changes to existing services. The main challenge is: How do you do that without impacting your existing consumers? Pivotal's recommendation is to leverage Consumer-driven contracts. That way, based on a common API contract, you can run integration tests between the consumer and a mock provider; and, between the real provider and a mock consumer; alll of this without setting up all your upstream or downstream systems. The main goal is to fail the build of the application when there is faulty integration, so together with unit and integration tests, your contract tests must have a place in your test suite.
This pattern is applicable in the context of either a single enterprise or a closed community of well-know services where providers have some influence over how consumers establish contracts with them.
With Spring Cloud Contract, you can successfully implement Consumer-driven Contracts for both JVM-based apps and non-JVM apps. You can use it for both HTTP-based and message-based interactions. Spring Cloud Contract greatly simplifies and significantly automates the maintenance of consumer-driven contracts for both producers and consumers. Let's see how it does it.
In this lab, we’ll explore writing producer- and consumer-side test cases through an HTTP interaction. To understand Spring Cloud Contract concepts, let's use it in the context of two (2) Spring Boots apps:
- The
PersonService
app (a.k.a. the producer) which provides an API to find a given person using his or her ID - The
MyAccount
app (a.k.a. the consumer) which accesses that API to get said person's names, email and phone.
- Create a folder in the filesystem and
cd
to it git clone https://github.com/Pivotal-Field-Engineering/s1p-2018-contract-testing.git
- Import it as an Existing Maven project into your IDE. Choose the
s1p-2018-contract-testing
folder as theRoot Directory
During the current Sprint, the PersonService
team has created the contract test's parent class: BaseClass.
In parallel, the MyAccount
team created the consumer-driver contract find_person_by_id.groovy; and provided it to the PersonService
team, which included the contract definition in the PersonService
codebase.
Feel free to review both BaseClass and the consumer contract using your IDE.
We are ready to build the app via these commands:
cd <YOUR_FOLDER>/s1p-2018-contract-testing/person-service
mvn clean package
This picture depicts what happens when you build the app:
- When the Maven build runs, Spring Cloud Contract Verifier use the contract definition to automatically generate full tests on your behalf
- You can use your IDE to view the source code of the generated test at:
/person-service/target/generated-test-sources/contracts/hello/HelloTest.java
- Once Spring Cloud Contract verifies that
PersonService
implementation is compliant with the contract, Maven generates and installs both stub artifact (person-service-0.0.1-SNAPSHOT-stubs.jar
) and thePersonService
app artifact (person-service-0.0.1-SNAPSHOT.jar
) in your designated Maven repo.
During the same Sprint, the MyAccount
app has also created a consumer-driven contract test to ensure the integration with the PersonService
app is aligned with the specifications.
Let's build the app:
cd <YOUR_FOLDER>/s1p-2018-contract-testing/myaccount-client
mvn clean package
This picture depicts what happens when you build the app:
- When the Maven build is executed, the Spring Cloud Contract Stub Runner in your JUnit test will automatically download the required stubs from your designated Maven repo
- The Spring Cloud Contract Stub Runner will also automatically start a WireMock server inside your test and feed it with the stubs it downloaded in the previous step
- Once Spring Cloud Contract verifies that
MyAccount
implementation is compliant with the contract, Maven generates and installs theMyAccount
artifact (myaccount-client-0.0.1-SNAPSHOT.jar
) in your designated Maven repo
- Open your IDE
- Find the
findPersonById
method in the PersonRestController - Change its annotation from
@GetMapping("/person/{id}")
to@GetMapping("/people/{id}")
. Save your changes. - Build the
PersonService
app - Was the build successful or it failed? Why?
- Change its annotation back to
@GetMapping("/person/{id}")
. Save your changes - Build the
PersonService
app
- Open your IDE
- Find the Person class
- Use your IDE to replace the
surname
string withlastname
. Save your changes - Build the
PersonService
app - Was the build successful or it failed? Why?
- Use your IDE to replace the
lastname
string withsurname
. Save your changes - Build the
PersonService
app
Now that we are happy with our local test, we need to deploy both PersonService
and MyAccount
apps to production on Pivotal Web Services. Let's get started.
In a new Terminal window, check if Concourse is already running
docker ps
If Concourse is running, you will see results similar to these:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
71d8a710ebb1 concourse/concourse "/usr/local/bin/dumb…" 23 hours ago Up 23 hours 0.0.0.0:8080->8080/tcp s1p-2018-contract-testing_concourse_1
21cb03f9e3a7 postgres "docker-entrypoint.s…" 23 hours ago Up 23 hours 5432/tcp s1p-2018-contract-testing_concourse-db_1
If Concourse is not running, issue this command
cd <YOUR_FOLDER>/s1p-2018-contract-testing
docker-compose start
Now login to Concourse via the fly
command-line:
fly login -t s1p -u test -p test -c http://127.0.0.1:8080
Customize the Concourse parameters file with your Pivotal Web Services settings. For example:
USERNAME: [email protected]
PASSWORD: n01d3@
ORG: S1Pdemo1
SPACE: development
PERSON-SERVICE-APP-NAME: gt-person-service
MYACCOUNT-CLIENT-APP-NAME: gt-myaccount-client
Set the pipeline and unpause it
cd <YOUR_FOLDER>/s1p-2018-contract-testing/ci
fly -t s1p set-pipeline -p deploy-s1p-2018 -c pipeline.yml -l params.yml
fly -t s1p unpause-pipeline --pipeline deploy-s1p-2018
In your browser, go to http://127.0.0.1:8080 with test/test as username and password.
Select the deploy-s1p-2018
pipeline.
To trigger the Concourse deployment pipeline, choose the deploy-person-service
box and the hit the +
sign on the right hand corner.
Wait a few minutes until the pipeline finishes deploying both apps to PWS. After the Concourse pipeline successfully completes, you should now see the entire pipeline as shown below:
To access the PersonService
, access this URL: https://PERSON-SERVICE-APP-NAME.cfapps.io/person/1
. You should get this result:
{"id":1,"name":"Person","surname":"One"}
To access the MyAccount
app, access this URL: https://MYACCOUNT-CLIENT-APP-NAME.cfapps.io/message/1
. You should get this result:
Hello Person One
Now that you successfully deployed both apps to PWS, try to exercise both service evolution scenarios:
- Change PersonService endpoint from /person to /people
- Change Person schema attribute from surname to lastname
IMPORTANT: Please remember to git push
your changes to the repo. The Concourse pipeline polls the Github repo for changes every minute.
Can you describe what happens?
If you would like to take a deeper dive, please take a look at Marcin's car rental example