This legacy app is a stereotypical monolith, with legacy code
that nobody loves. It has an anemic structure. We’ve left just
one domain which is the CustomerRentalHistory
. It will call this API
https://stripe.com/docs/api/java#list_charges
Set up a scenario. We have a problem with a legacy service. Let’s see how
Spring Cloud Contract can solve this. The legacy app is the stripe.com API,
fronting a mainframe, it offers many API’s one of which is the CustomerRentalHistory
API returns JSON response containing lots of fields about the customer including
their name / address / and how many rentals they have made in the past. For simplicity sake
we’re returning last 25 charges.
A explains the legacy API (https://stripe.com/docs/api/java#list_charges)
to M and talks through the kind of problems he has seen with
the API & which fields he only cares about. Amount
, Captured
, Customer
A provides a tour of the client side code which currently exists to call the
CustomerRentalHistory
service.
M steps in takes over the keyboard and show A how to write a contract test to generate a stub for the rental service A asks questions to clarify and reinforce what M is doing.
-
We don’t want to fake the response manually cause we want to verify the integrations
-
how can we do that?
-
who knows WireMock?
-
what is a stub?
-
-
In
stubs
-
we add
spring-cloud-contract-maven-plugin
section -
we add the
<skipTests>true</skipTests>
cause we don’t want to run tests -
we add the
starter-verifier
dependency -
we write the first contract that returns the contents of the
charges.json
-
when we do
./mvnw clean install -pl com.example:the-legacy-app-stubs
we will see that stubs got created
-
-
In
impl
-
we write a test that asserts that the response contains 25 results
-
we run it - will it pass? Of course not…
-
we add
spring-cloud-starter-contract-stubrunner
-
we write a test that uses
@AutoConfigureStubRunner
and works offline to download stubs -
we assert that the response contains 25 results
-
hint, pick this or explain proxying via WireMock
A asks a question, what good is my stub if it wasn’t tested against the real API.
Ok, so, from the contracts let’s generate the tests to see if the result is valid.
Let’s write some code!
-
Let’s define in the plugin the
EXPLICIT
mode of tests. We want to send a real request -
we need to change the contract cause Stripe generates the response data. Our json will not pass these validations. That’s why in Sc-Contract we have the dynamic and static bits. For the test we will call an assertion function and for the stub side we will return a fixed value from the file
-
the value from the file can be updated just before the test
-
the value can be taken not from the file but from the external API
-
there are different ways, but for simplicity’s sake we’ll take it from the file
-
-
let’s create a base class
-
we create the method
assertResponse(…)
-
we set up the URI for the call
-
we set up basic authentication
-
Now we can execute the build (debug from IDE) and we’ll see that we’ve managed to connect to Stripe and assert the response.
hint, pick this or explain proxying via DSL
How can we generate stubs in the easiest way? So that they are validated and reusable? We can create a proxy via which we will send requests to the real app! Then from this proxy we will store all the stubs in our output directory.
Thanks to the Maven assembly
plugin, we can then package the stubs in a JAR
in the structure that is understood by the Stub Runner. That way the captured
responses from the real API can be reused offline in your company!
At the end of the session
-
A’s code base has an extra contract test in it
-
The maven plugin has been added to the project
-
The contract is in the client side code base
-
we create a submodule with
src/test/resources/contracts
-
setup maven plugin to first generate tests (which will fail)
-
then we set the maven plugin NOT to generate tests, only generate stubs because we don’t own the production code those stubs will be reused in the stub runner tests
-
-
A test with StubRunner has been written and executed
-
The producer side test has not been executed at this point
A then asks where the producer test should live. A says he is good friends with the developers of the bad API and A builds a pull request for A’s git repo which has the contracts in it and demonstrates how A can integrate the app into the tests. Explain that ones the contracts are validated against the producer then we’re talking about producer contracts.
M says how to move the contracts to an external repository it happens that we already have one prepared here:
$ git clone https://github.com/marcingrzejszczak/2017-s1p-external-contracts.git
M explains the structure:
-
contracts lay under
main
cause we need to build the JAR with contracts -
explain the assembly plugin
-
each application has its own slash separated
groupid
/artifactid
folder structure-
under that folder it’s good practice for each consumer to their own subfolder
-
we don’t want to run tests here (that’s why we skip them)
-
Time for action:
-
we create the missing
src/main/resources/com/example/customer-rental-history
folder -
we set up the
pom.xml
forcustomer-rental-history
contracts (copy it fromdone
folder) -
we do
mvn clean install
to install the stubs locally and update the tests in the IDE to point to these stubs -
we’ve proven that we can run tests locally against the stubs from the contract
-
we
cd
back to root and do./mvnw clean install
- we show the JAR with all contracts
At the end of the session
-
A separate, single repository with all the contracts got created
-
A’s code base no longer uses the contracts from the stub module cause the stubs come from the external repo
-
That will work only under the assumption that the
CustomerRentalHistory
team will start doing contract testing
-
-
Now, all teams can store contracts in that repository and collaborate
-
We’re ready to make
the legacy application
test its own API
A realizes that he is just as bad as CustomerRentalHistory
with his clients
and now A wants to adopt SCC for his FraudDetectionService.
-
we setup SCC for FDS
-
to simplify the process we will store the contracts with the app
-
-
copy the contracts for the legacy app with
stubsPerConsumer
structure-
let’s assume that there are 2 clients
audit-service
andinsurance-service
-
audit
needs themessage
field -
insurance
needs themessage
&ex27
fields
-
M explains the idea
A asks, can I delete ex27
field? I don’t remember if anybody uses it…
A explains what consumer contracts are.
What does this actually mean in the context of CD (SLIDES TIME) - Can you safely delete a field in your API? - Have my consumers changed and break me? - Are my API changes backward compatible?