From bffe539f4bbfe2e9c7790ba12d1606fd1f28f3c9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 25 Jul 2019 14:42:04 +0200 Subject: [PATCH] Working docs --- docs/src/main/asciidoc/_getting-started.adoc | 1137 ---------------- .../main/asciidoc/documentation-overview.adoc | 4 - docs/src/main/asciidoc/getting-started.adoc | 1139 ++++++++++++++++- 3 files changed, 1138 insertions(+), 1142 deletions(-) delete mode 100644 docs/src/main/asciidoc/_getting-started.adoc diff --git a/docs/src/main/asciidoc/_getting-started.adoc b/docs/src/main/asciidoc/_getting-started.adoc deleted file mode 100644 index 50ed913b7c..0000000000 --- a/docs/src/main/asciidoc/_getting-started.adoc +++ /dev/null @@ -1,1137 +0,0 @@ -include::_attributes.adoc[] - -If you are getting started with {project-full-name}, or "`Spring`" in general, start by reading -this section. It answers the basic "`what?`", "`how?`" and "`why?`" questions. It -includes an introduction to {project-full-name}, along with installation instructions. We then -walk you through building your first {project-full-name} application, discussing some core -principles as we go. - -[[getting-started-introducing-spring-cloud-contract]] -== Introducing Spring Cloud Contract - -Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. - -[[getting-started-introducing-spring-cloud-contract-history]] -=== History - -Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. -It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] -from (https://github.com/Codearte[Codearte]). - -The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. - -[[getting-started-introducing-spring-cloud-contract-why]] -==== Why would you need it? - -Assume that we have a system consisting of multiple microservices: - -image::images/Deps.png[Microservices Architecture] - -[[getting-started-introducing-spring-cloud-contract-testing-issues]] -==== Testing issues - -If we wanted to test the application in top left corner to determine whether it can -communicate with other services, we could do one of two things: - -- Deploy all microservices and perform end-to-end tests. -- Mock other microservices in unit/integration tests. - -Both have their advantages but also a lot of disadvantages. - -*Deploy all microservices and perform end to end tests* - -Advantages: - -- Simulates production. -- Tests real communication between services. - -Disadvantages: - -- To test one microservice, we have to deploy 6 microservices, a couple of databases, -etc. -- The environment where the tests run is locked for a single suite of tests (nobody else -would be able to run the tests in the meantime). -- They take a long time to run. -- The feedback comes very late in the process. -- They are extremely hard to debug. - -*Mock other microservices in unit/integration tests* - -Advantages: - -- They provide very fast feedback. -- They have no infrastructure requirements. - -Disadvantages: - -- The implementor of the service creates stubs that might have nothing to do with -reality. -- You can go to production with passing tests and failing production. - -To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the -whole world of microservices. If you work on stubs, then the only applications you need -are those that your application directly uses. - -image::images/Stubs2.png[Stubbed Services] - -Spring Cloud Contract gives you the certainty that the stubs that you use were -created by the service that you're calling. Also, if you can use them, it means that they -were tested against the producer's side. In short, you can trust those stubs. - -[[getting-started-introducing-spring-cloud-contract-purposes]] -=== Purposes - -The main purposes of Spring Cloud Contract are: - -- To ensure that HTTP / Messaging stubs (used when developing the client) do exactly -what the actual server-side implementation does. -- To promote ATDD (acceptance test driven developement) method and Microservices architectural style. -- To provide a way to publish changes in contracts that are immediately visible on both sides. -- To generate boilerplate test code to be used on the server side. - -By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. - -IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business -features in the contracts. Assume that we have a business use case of fraud check. If a -user can be a fraud for 100 different reasons, we would assume that you would create 2 -contracts, one for the positive case and one for the negative case. Contract tests are -used to test contracts between applications and not to simulate full behavior. - -[[getting-started-what-is-a-contract]] -=== What is a contract? - -As consumers of services, we need to define what exactly we want to achieve. We need to -formulate our expectations. That is why we write contracts. In other words a contract is an agreement on how the API / message communication should look like. Let's look at the following example - -Assume that you want to send a request containing the ID of a client company and the -amount it wants to borrow from us. You also want to send it to the /fraudcheck url via -the PUT method. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -[[getting-started-three-second-tour]] -== A Three-second Tour - -This very brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find a somewhat longer tour -<>. - -[plantuml, getting-started-three-second, png] ----- -"API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin -"API Producer"->"API Producer": add SCC Verifier dependency -"API Producer"->"API Producer": define contracts -"API Producer"->"Build": run build -"Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) -"Build"->"Stub Storage": upload contracts \nand stubs and the project arifact -"Build"->"API Producer": Build successful -"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency -"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Stub Storage": test asks for [API Producer] stubs -"Stub Storage"->"SCC Stub Runner": fetch the [API Producer] stubs -"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs -"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub -"SCC Stub Runner"->"API Consumer": communication is correct ----- - -[[getting-started-three-second-tour-producer]] -=== On the Producer Side - -To start working with Spring Cloud Contract, add files with `REST`/messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -Then add the Spring Cloud Contract Verifier dependency and plugin to your build file, as -shown in the following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the tests get generated under -`org.springframework.cloud.contract.verifier.tests.`. - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of either handling HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests, and it -should contain all the setup necessary to run them (for example `RestAssuredMockMvc` -controller setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -The changes can now be merged, and both the application and the stub artifacts may be -published in an online repository. - -[[getting-started-three-second-tour-consumer]] -=== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To do so, add the dependency to `Spring Cloud Contract Stub Runner`, as shown in the -following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the stubs -by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -TIP: The tests are being skipped because the Producer-side contract implementation is not -in place yet, so the automatically-generated contract tests fail. -* By getting already-existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URL as `Spring Cloud Contract -Stub Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` values for `Spring Cloud Contract Stub Runner` to -run the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -Now, in your integration test, you can receive stubbed versions of HTTP responses or -messages that are expected to be emitted by the collaborator service. - -[[getting-started-first-application]] -== Developing Your First Spring Cloud Contract based application - -This brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find an even more brief tour -<>. - -For the sake of this example the `Stub Storage` will be Nexus / Artifactory. - -image::getting-started-three-second.png[Getting started first application] - -[[getting-started-first-application-producer]] -=== On the Producer Side - -To start working with `Spring Cloud Contract`, add Spring Cloud Contract Verifier dependency and plugin to your build file, -as shown in the following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Now let's add files with `REST/` messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -For the HTTP stubs, a contract defines what kind of response should be returned for a -given request (taking into account the HTTP methods, URLs, headers, status codes, and so -on). The following example shows how an HTTP stub contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -package contracts - -org.springframework.cloud.contract.spec.Contract.make { - request { - method 'PUT' - url '/fraudcheck' - body([ - "client.id": $(regex('[0-9]{10}')), - loanAmount: 99999 - ]) - headers { - contentType('application/json') - } - } - response { - status OK() - body([ - fraudCheckStatus: "FRAUD", - "rejection.reason": "Amount too high" - ]) - headers { - contentType('application/json') - } - } -} ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -request: - method: PUT - url: /fraudcheck - body: - "client.id": 1234567890 - loanAmount: 99999 - headers: - Content-Type: application/json - matchers: - body: - - path: $.['client.id'] - type: by_regex - value: "[0-9]{10}" -response: - status: 200 - body: - fraudCheckStatus: "FRAUD" - "rejection.reason": "Amount too high" - headers: - Content-Type: application/json;charset=UTF-8 ----- -==== - -In the case of messaging, you can define: - -* The input and the output messages can be defined (taking into account from and where it -was sent, the message body, and the header). -* The methods that should be called after the message is received. -* The methods that, when called, should trigger a message. - -The following example shows a Camel messaging contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] ----- -==== - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the generated tests are under -`org.springframework.cloud.contract.verifier.tests.`. - -The generated tests will differ, depending on which framework and test type you've setup in your plugin. - -In the samples below you'll find: - -- the default test mode for HTTP contracts via `MockMvc` -- JAX-RS client via the `JAXRS` test mode -- `WebTestClient` based test (this is particularly recommended while working with Reactive, `Web-Flux`-based applications) set via `WEBTESTCLIENT` test mode -- Spock based test via the `testFramework` property set to `SPOCK` - -==== -[source,java,indent=0,role="primary"] -.mockmvc ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -[source,java,indent=0,role="secondary"] -.jaxrs ----- -@SuppressWarnings("rawtypes") -public class FooTest { - WebTarget webTarget; - - @Test - public void validate_() throws Exception { - - // when: - Response response = webTarget - .path("/users") - .queryParam("limit", "10") - .queryParam("offset", "20") - .queryParam("filter", "email") - .queryParam("sort", "name") - .queryParam("search", "55") - .queryParam("age", "99") - .queryParam("name", "Denis.Stepanov") - .queryParam("email", "bob@email.com") - .request() - .build("GET") - .invoke(); - String responseAsString = response.readEntity(String.class); - - // then: - assertThat(response.getStatus()).isEqualTo(200); - - // and: - DocumentContext parsedJson = JsonPath.parse(responseAsString); - assertThatJson(parsedJson).field("['property1']").isEqualTo("a"); - } - -} ----- - -[source,java,indent=0,role="secondary"] -.webtestclient ----- -@Test - public void validate_shouldRejectABeerIfTooYoung() throws Exception { - // given: - WebTestClientRequestSpecification request = given() - .header("Content-Type", "application/json") - .body("{\"age\":10}"); - - // when: - WebTestClientResponse response = given().spec(request) - .post("/check"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK"); - } ----- - -[source,groovy,indent=0,role="secondary"] -.spock ----- -given: - ContractVerifierMessage inputMessage = contractVerifierMessaging.create( - \'\'\'{"bookName":"foo"}\'\'\', - ['sample': 'header'] - ) - -when: - contractVerifierMessaging.send(inputMessage, 'jms:delete') - -then: - noExceptionThrown() - bookWasDeleted() ----- -==== - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of handling either HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests and should -contain all the setup necessary to run them (for example, `RestAssuredMockMvc` controller -setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -Information about installing the stubs jar to the local repository appears in the logs, as -shown in the following example: - -[source,bash,indent=0] ----- - [INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar - [INFO] - [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar - [INFO] - [INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- - [INFO] - [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar - [INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -You can now merge the changes and publish both the application and the stub artifacts -in an online repository. - -[[getting-started-first-application-consumer]] -=== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To get started, add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the -stubs by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -NOTE: The tests are skipped because the Producer-side contract implementation is not yet -in place, so the automatically-generated contract tests fail. -* Getting already existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URl as `Spring Cloud Contract Stub -Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` for `Spring Cloud Contract Stub Runner` to run -the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -In your integration test, you can receive stubbed versions of HTTP responses or messages -that are expected to be emitted by the collaborator service. You can see entries similar -to the following in the build logs: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -[[getting-started-cdc]] -== Step-by-step Guide to Consumer Driven Contracts (CDC) with contracts laying on the producer side - -Consider an example of Fraud Detection and the Loan Issuance process. The business -scenario is such that we want to issue loans to people but do not want them to steal from -us. The current implementation of our system grants loans to everybody. - -Assume that `Loan Issuance` is a client to the `Fraud Detection` server. In the current -sprint, we must develop a new feature: if a client wants to borrow too much money, then -we mark the client as a fraud. - -Technical remarks - -* Fraud Detection has an `artifact-id` of `http-server`, -* Loan -Issuance has an artifact-id of `http-client` -* both have a `group-id` of `com.example` -* For the sake of this example the `Stub Storage` will be Nexus / Artifactory - -Social remarks - -* both client and server development teams need to communicate directly and -discuss changes while going through the process -* CDC is all about communication. - -The https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-server[server -side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-client[the -client code here]. - -TIP: In this case, the producer owns the contracts. Physically, all the contract are -in the producer's repository. - -[[getting-started-cdc-technical-note]] -=== Technical note - -If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the -following section to your build: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repos,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0] ----- - -For simplicity we will use the following acronyms: - -- Loan Issuance - LI - the http client -- Fraud Detection - FD - the http server -- Spring Cloud Contract - SCC - -=== Consumer side (Loan Issuance) - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps: - -. Start doing TDD by writing a test for your feature. -. Write the missing implementation. -. Clone the Fraud Detection service repository locally. -. Define the contract locally in the repo of Fraud Detection service. -. Add the Spring Cloud Contract (SCC) plugin. -. Run the integration tests. -. File a pull request. -. Create an initial implementation. -. Take over the pull request. -. Write the missing implementation. -. Deploy your app. -. Work online. - -Let's start with the Loan Issuance flow: - -[plantuml, getting-started-cdc-client, png] ----- -"Loan\nIssuance"->"Loan\nIssuance": start doing TDD\nby writing a test\nfor your feature -"Loan\nIssuance"->"Loan\nIssuance": write the \nmissing implementation -"Loan\nIssuance"->"Loan\nIssuance": run a test - it fails\ndue to no server running -"Loan\nIssuance"->"Fraud\nDetection\nClone": clone the repository -"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add missing dependencies\n& define contracts -"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add the SCC plugin -"Fraud\nDetection\nClone"->"FD \nClone Build": install the stubs locally -"FD \nClone Build"->"SCC Plugin \nin FD Clone": generate stubs \nand stubs \nartifact (e.g. stubs-jar) -"SCC Plugin \nin FD Clone"->"FD \nClone Build": stubs and artifacts\ngenerated -"FD \nClone Build"->"Local storage": install the stubs locally -"Local storage"->"FD \nClone Build": stub sucessfully installed -"FD \nClone Build"->"Fraud\nDetection\nClone": build successful -"Loan\nIssuance"->"Loan\nIssuance": add a SCC\nStub Runner\ndependency\nand setup -"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nlocal storage -"LI\nSCC\nStub Runner"->"Local storage": find stubs of [FD] -"Local storage"->"LI\nSCC\nStub Runner": stubs of [FD] found -"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] -"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running -"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test -"Loan\nIssuance"->"Loan\nIssuance": run a test -"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub -"FD stub"->"Loan\nIssuance": stub responds successfuly -"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully -"Loan\nIssuance"->"Fraud\nDetection": send a pull request\nwith the\nsuggested contracts ----- - -*Start doing TDD by writing a test for your feature.* - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0] ----- - -Assume that you have written a test of your new feature. If a loan application for a big -amount is received, the system should reject that loan application with some description. - -*Write the missing implementation.* - -At some point in time, you need to send a request to the Fraud Detection service. Assume -that you need to send the request containing the ID of the client and the amount the -client wants to borrow. You want to send it to the `/fraudcheck` url via the `PUT` method. - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] ----- - -For simplicity, the port of the Fraud Detection service is set to `8080`, and the -application runs on `8090`. - -If you start the test at this point, it breaks, because no service currently runs on port -`8080`. - -*Clone the Fraud Detection service repository locally.* - -You can start by playing around with the server side contract. To do so, you must first -clone it. - -[source,bash,indent=0] ----- -$ git clone https://your-git-server.com/server-side.git local-http-server-repo ----- - -*Define the contract locally in the repo of Fraud Detection service.* - -As a consumer, you need to define what exactly you want to achieve. You need to formulate -your expectations. To do so, write the following contract: - -IMPORTANT: Place the contract under `src/test/resources/contracts/fraud` folder. The `fraud` folder -is important because the producer's test base class name references that folder. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -The YML contract is quite straight-forward. However when you take a look at the Contract -written using a statically typed Groovy DSL - you might wonder what the -`value(client(...), server(...))` parts are. By using this notation, Spring Cloud -Contract lets you define parts of a JSON block, a URL, etc., which are dynamic. In case -of an identifier or a timestamp, you need not hardcode a value. You want to allow some -different ranges of values. To enable ranges of values, you can set regular expressions -matching those values for the consumer side. You can provide the body by means of either -a map notation or String with interpolations. -//TODO: Ensure that the links are correct -Consult the <> section for more information. We highly recommend using the map notation! - -TIP: You must understand the map notation in order to set up contracts. Please read the -https://groovy-lang.org/json.html[Groovy docs regarding JSON]. - -The previously shown contract is an agreement between two sides that: - -- if an HTTP request is sent with all of -** a `PUT` method on the `/fraudcheck` endpoint, -** a JSON body with a `client.id` that matches the regular expression `[0-9]{10}` and -`loanAmount` equal to `99999`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`, -- then an HTTP response is sent to the consumer that -** has status `200`, -** contains a JSON body with the `fraudCheckStatus` field containing a value `FRAUD` and -the `rejectionReason` field having value `Amount too high`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`. - -Once you are ready to check the API in practice in the integration tests, you need to -install the stubs locally. - -*Add the Spring Cloud Contract Verifier plugin.* - -We can add either a Maven or a Gradle plugin. In this example, you see how to add Maven. -First, add the `Spring Cloud Contract` BOM. - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_bom,indent=0] ----- - -Next, add the `Spring Cloud Contract Verifier` Maven plugin - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -Since the plugin was added, you get the `Spring Cloud Contract Verifier` features which, -from the provided contracts: - -- generate and run tests -- produce and install stubs - -You do not want to generate tests since you, as the consumer, want only to play with the -stubs. You need to skip the test generation and execution. When you execute: - -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- - -In the logs, you see something like this: - -[source,bash,indent=0] ----- -[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar -[INFO] -[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar -[INFO] -[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- -[INFO] -[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar -[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -The following line is extremely important: - -[source,bash,indent=0] ----- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -It confirms that the stubs of the `http-server` have been installed in the local -repository. - -*Run the integration tests.* - -In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic -stub downloading, you must do the following in your consumer side project (`Loan -Application service`): - -Add the `Spring Cloud Contract` BOM: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=contract_bom,indent=0] ----- - -Add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -Annotate your test class with `@AutoConfigureStubRunner`. In the annotation, provide the -`group-id` and `artifact-id` for the Stub Runner to download the stubs of your -collaborators. (Optional step) Because you're playing with the collaborators offline, you -can also provide the offline work switch (`StubRunnerProperties.StubsMode.LOCAL`). - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] ----- - -Now, when you run your tests, you see something like this: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -This output means that Stub Runner has found your stubs and started a server for your app -with group id `com.example`, artifact id `http-server` with version `0.0.1-SNAPSHOT` of -the stubs and with `stubs` classifier on port `8080`. - -*File a pull request.* - -What you have done until now is an iterative process. You can play around with the -contract, install it locally, and work on the consumer side until the contract works as -you wish. - -Once you are satisfied with the results and the test passes, publish a pull request to -the server side. Currently, the consumer side work is done. - -=== Producer side (Fraud Detection server) - -As a developer of the Fraud Detection server (a server to the Loan Issuance service) we would like to: - -- take over the pull request -- write the missing implementation -- deploy the application - -Let's look at the Fraud Detection flow: - -[plantuml, getting-started-cdc-server, png] ----- -"Fraud\nDetection"->"Fraud\nDetection": take over the\n pull request -"Fraud\nDetection"->"Fraud\nDetection": setup\nSpring Cloud\nContract plugin -"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build -"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) -"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated -"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests -"Fraud\nDetection\nBuild"->"Fraud\nDetection": generated tests failed! -"Fraud\nDetection"->"Fraud\nDetection": setup\nbase classes\nfor contract tests -"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build -"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) -"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated -"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests -"Fraud\nDetection\nBuild"->"Fraud\nDetection": all the tests passed! -"Fraud\nDetection"->"Fraud\nDetection": commit and push changes -"Fraud\nDetection"->"CI": commit pushed!\nTriggers the build -"CI"->"Stub Storage": build successful,\nupload artifacts ----- - -*Take over the pull request.* - -As a reminder, you can see the initial implementation here: - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -[source,bash,indent=0] ----- -$ git checkout -b contract-change-pr master -$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr ----- - -You must add the dependencies needed by the autogenerated tests: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -In the configuration of the Maven plugin, pass the `packageWithBaseClasses` property - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -IMPORTANT: This example uses "convention based" naming by setting the -`packageWithBaseClasses` property. Doing so means that the two last packages combine to -make the name of the base test class. In our case, the contracts were placed under -`src/test/resources/contracts/fraud`. Since you do not have two packages starting from -the `contracts` folder, pick only one, which should be `fraud`. Add the `Base` suffix and -capitalize `fraud`. That gives you the `FraudBase` test class name. - -All the generated tests extend that class. Over there, you can set up your Spring Context -or whatever is necessary. In this case, use https://github.com/rest-assured/rest-assured[Rest Assured MVC] to -start the server side `FraudDetectionController`. - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/java/com/example/fraud/FraudBase.java[] ----- - -Now, if you run the `./mvnw clean install`, you get something like this: - -[source,bash,indent=0] ----- -Results : - -Tests in error: - ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed... ----- - -This error occurs because you have a new contract from which a test was generated and it -failed since you have not implemented the feature. The auto-generated test would look -like this: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -If you used the Groovy DSL, you can see, all the `producer()` parts of the Contract that were present in the -`value(consumer(...), producer(...))` blocks got injected into the test. -In case of using YAML, the same applied for the `matchers` sections of the `response`. - -Note that, on the producer side, you are also doing TDD. The expectations are expressed -in the form of a test. This test sends a request to our own application with the URL, -headers, and body defined in the contract. It also is expecting precisely defined values -in the response. In other words, you have the `red` part of `red`, `green`, and -`refactor`. It is time to convert the `red` into the `green`. - -*Write the missing implementation.* - -Because you know the expected input and expected output, you can write the missing -implementation: - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -When you execute `./mvnw clean install` again, the tests pass. Since the `Spring Cloud -Contract Verifier` plugin adds the tests to the `generated-test-sources`, you can -actually run those tests from your IDE. - -*Deploy your app.* - -Once you finish your work, you can deploy your change. First, merge the branch: - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr -$ git push origin master ----- - -Your CI might run something like `./mvnw clean deploy`, which would publish both the -application and the stub artifacts. - -=== Consumer Side (Loan Issuance) Final Step - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server) we will want to: - -- merge our feature branch to `master` -- switch to online mode of working - - -[plantuml, getting-started-cdc-client, png] ----- -"Loan\nIssuance"->"Loan\nIssuance": merge the\nfeature branch\nto master branch -"Loan\nIssuance"->"Loan\nIssuance": setup SCC Stub Runner\nto fetch stubs\nfrom Stub Storage -"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nStub Storage -"LI\nSCC\nStub Runner"->"Stub Storage": find stubs of [FD] -"Stub Storage"->"LI\nSCC\nStub Runner": stubs of [FD] found -"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] -"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running -"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test -"Loan\nIssuance"->"Loan\nIssuance": run a test -"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub -"FD stub"->"Loan\nIssuance": stub responds successfuly -"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully ----- - -*Merge branch to master.* - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr ----- - -*Work online.* - -Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate -where the repository with your stubs is located. At this moment the stubs of the server -side are automatically downloaded from Nexus/Artifactory. You can set the value of -`stubsMode` to `REMOTE`. The following code shows an example of -achieving the same thing by changing the properties. - -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -That's it! - -[[getting-started-whats-next]] -== What to Read Next -Hopefully, this section provided some of the {project-full-name} basics and got you on your way -to writing your own applications. If you are a task-oriented type of developer, you might -want to jump over to https://spring.io and check out some of the -https://spring.io/guides/[getting started] guides that solve specific "`How do I do that -with Spring?`" problems. We also have {project-full-name}-specific -"`<>`" reference documentation. - -Otherwise, the next logical step is to read _<>_. If -you are really impatient, you could also jump ahead and read about -_<>_. - -// TODO: Check if we can link courses -In addition to that you can check out the following videos: - -- "Consumer Driven Contracts and Your Microservice Architecture" by Olga Maciaszek-Sharma and Marcin Grzejszczak - -video::pDkC_00hhvA[youtube,width=640,height=480] - -- "Contract Tests in the Enterprise" by Marcin Grzejszczak - -video::ZyHG-VOzPZg[youtube,width=640,height=480] - -- "Why Contract Tests Matter?" by Marcin Grzejszczak - -video::TvpkZu1e2Dc[youtube,start=6262,width=640,height=480] - -You can find the default project samples at -https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. - -You can find the Spring Cloud Contract workshops https://cloud-samples.spring.io/spring-cloud-contract-samples/[over here]. \ No newline at end of file diff --git a/docs/src/main/asciidoc/documentation-overview.adoc b/docs/src/main/asciidoc/documentation-overview.adoc index d22f628f67..b70c364876 100644 --- a/docs/src/main/asciidoc/documentation-overview.adoc +++ b/docs/src/main/asciidoc/documentation-overview.adoc @@ -108,8 +108,4 @@ Finally, we have a few topics for more advanced users: <> | <> | <> -* *Appendix:* -<> | -<> | -<> diff --git a/docs/src/main/asciidoc/getting-started.adoc b/docs/src/main/asciidoc/getting-started.adoc index 6b369a278f..ce8d92c0ee 100644 --- a/docs/src/main/asciidoc/getting-started.adoc +++ b/docs/src/main/asciidoc/getting-started.adoc @@ -1,4 +1,1141 @@ [[getting-started]] = Getting Started include::_attributes.adoc[] -include::_getting-started.adoc[] \ No newline at end of file + +include::_attributes.adoc[] + +If you are getting started with {project-full-name}, or "`Spring`" in general, start by reading +this section. It answers the basic "`what?`", "`how?`" and "`why?`" questions. It +includes an introduction to {project-full-name}, along with installation instructions. We then +walk you through building your first {project-full-name} application, discussing some core +principles as we go. + +[[getting-started-introducing-spring-cloud-contract]] +== Introducing Spring Cloud Contract + +Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. + +[[getting-started-introducing-spring-cloud-contract-history]] +=== History + +Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. +It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] +from (https://github.com/Codearte[Codearte]). + +The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. + +[[getting-started-introducing-spring-cloud-contract-why]] +==== Why would you need it? + +Assume that we have a system consisting of multiple microservices: + +image::images/Deps.png[Microservices Architecture] + +[[getting-started-introducing-spring-cloud-contract-testing-issues]] +==== Testing issues + +If we wanted to test the application in top left corner to determine whether it can +communicate with other services, we could do one of two things: + +- Deploy all microservices and perform end-to-end tests. +- Mock other microservices in unit/integration tests. + +Both have their advantages but also a lot of disadvantages. + +*Deploy all microservices and perform end to end tests* + +Advantages: + +- Simulates production. +- Tests real communication between services. + +Disadvantages: + +- To test one microservice, we have to deploy 6 microservices, a couple of databases, +etc. +- The environment where the tests run is locked for a single suite of tests (nobody else +would be able to run the tests in the meantime). +- They take a long time to run. +- The feedback comes very late in the process. +- They are extremely hard to debug. + +*Mock other microservices in unit/integration tests* + +Advantages: + +- They provide very fast feedback. +- They have no infrastructure requirements. + +Disadvantages: + +- The implementor of the service creates stubs that might have nothing to do with +reality. +- You can go to production with passing tests and failing production. + +To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the +whole world of microservices. If you work on stubs, then the only applications you need +are those that your application directly uses. + +image::images/Stubs2.png[Stubbed Services] + +Spring Cloud Contract gives you the certainty that the stubs that you use were +created by the service that you're calling. Also, if you can use them, it means that they +were tested against the producer's side. In short, you can trust those stubs. + +[[getting-started-introducing-spring-cloud-contract-purposes]] +=== Purposes + +The main purposes of Spring Cloud Contract are: + +- To ensure that HTTP / Messaging stubs (used when developing the client) do exactly +what the actual server-side implementation does. +- To promote ATDD (acceptance test driven developement) method and Microservices architectural style. +- To provide a way to publish changes in contracts that are immediately visible on both sides. +- To generate boilerplate test code to be used on the server side. + +By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. + +IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business +features in the contracts. Assume that we have a business use case of fraud check. If a +user can be a fraud for 100 different reasons, we would assume that you would create 2 +contracts, one for the positive case and one for the negative case. Contract tests are +used to test contracts between applications and not to simulate full behavior. + +[[getting-started-what-is-a-contract]] +=== What is a contract? + +As consumers of services, we need to define what exactly we want to achieve. We need to +formulate our expectations. That is why we write contracts. In other words a contract is an agreement on how the API / message communication should look like. Let's look at the following example + +Assume that you want to send a request containing the ID of a client company and the +amount it wants to borrow from us. You also want to send it to the /fraudcheck url via +the PUT method. + +==== +[source,groovy,indent=0,role="primary"] +.groovy +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] +---- + +[source,yaml,indent=0,role="secondary"] +.yaml +---- +include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] +---- +==== + +[[getting-started-three-second-tour]] +== A Three-second Tour + +This very brief tour walks through using Spring Cloud Contract: + +* <> +* <> + +You can find a somewhat longer tour +<>. + +[plantuml, getting-started-three-second, png] +---- +"API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin +"API Producer"->"API Producer": add SCC Verifier dependency +"API Producer"->"API Producer": define contracts +"API Producer"->"Build": run build +"Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) +"Build"->"Stub Storage": upload contracts \nand stubs and the project arifact +"Build"->"API Producer": Build successful +"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency +"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test +"SCC Stub Runner"->"Stub Storage": test asks for [API Producer] stubs +"Stub Storage"->"SCC Stub Runner": fetch the [API Producer] stubs +"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs +"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub +"SCC Stub Runner"->"API Consumer": communication is correct +---- + +[[getting-started-three-second-tour-producer]] +=== On the Producer Side + +To start working with Spring Cloud Contract, add files with `REST`/messaging contracts +expressed in either Groovy DSL or YAML to the contracts directory, which is set by the +`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. + +Then add the Spring Cloud Contract Verifier dependency and plugin to your build file, as +shown in the following example: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] +---- + +The following listing shows how to add the plugin, which should go in the build/plugins +portion of the file: + +[source,xml,indent=0] +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + +---- + +Running `./mvnw clean install` automatically generates tests that verify the application +compliance with the added contracts. By default, the tests get generated under +`org.springframework.cloud.contract.verifier.tests.`. + +As the implementation of the functionalities described by the contracts is not yet +present, the tests fail. + +To make them pass, you must add the correct implementation of either handling HTTP +requests or messages. Also, you must add a correct base test class for auto-generated +tests to the project. This class is extended by all the auto-generated tests, and it +should contain all the setup necessary to run them (for example `RestAssuredMockMvc` +controller setup or messaging test setup). + +Once the implementation and the test base class are in place, the tests pass, and both the +application and the stub artifacts are built and installed in the local Maven repository. +The changes can now be merged, and both the application and the stub artifacts may be +published in an online repository. + +[[getting-started-three-second-tour-consumer]] +=== On the Consumer Side + +`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running +WireMock instance or messaging route that simulates the actual service. + +To do so, add the dependency to `Spring Cloud Contract Stub Runner`, as shown in the +following example: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] +---- + +You can get the Producer-side stubs installed in your Maven repository in either of two +ways: + +* By checking out the Producer side repository and adding contracts and generating the stubs +by running the following commands: ++ +[source,bash,indent=0] +---- +$ cd local-http-server-repo +$ ./mvnw clean install -DskipTests +---- +TIP: The tests are being skipped because the Producer-side contract implementation is not +in place yet, so the automatically-generated contract tests fail. +* By getting already-existing producer service stubs from a remote repository. To do so, +pass the stub artifact IDs and artifact repository URL as `Spring Cloud Contract +Stub Runner` properties, as shown in the following example: ++ +[source,yaml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +---- + +Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, +provide the `group-id` and `artifact-id` values for `Spring Cloud Contract Stub Runner` to +run the collaborators' stubs for you, as shown in the following example: + +[source,java, indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.NONE) +@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, + stubsMode = StubRunnerProperties.StubsMode.LOCAL) +public class LoanApplicationServiceTests { +---- + +TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and +`LOCAL` for offline work. + +Now, in your integration test, you can receive stubbed versions of HTTP responses or +messages that are expected to be emitted by the collaborator service. + +[[getting-started-first-application]] +== Developing Your First Spring Cloud Contract based application + +This brief tour walks through using Spring Cloud Contract: + +* <> +* <> + +You can find an even more brief tour +<>. + +For the sake of this example the `Stub Storage` will be Nexus / Artifactory. + +image::getting-started-three-second.png[Getting started first application] + +[[getting-started-first-application-producer]] +=== On the Producer Side + +To start working with `Spring Cloud Contract`, add Spring Cloud Contract Verifier dependency and plugin to your build file, +as shown in the following example: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] +---- + +The following listing shows how to add the plugin, which should go in the build/plugins +portion of the file: + +[source,xml,indent=0] +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + +---- + +Now let's add files with `REST/` messaging contracts +expressed in either Groovy DSL or YAML to the contracts directory, which is set by the +`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. + +For the HTTP stubs, a contract defines what kind of response should be returned for a +given request (taking into account the HTTP methods, URLs, headers, status codes, and so +on). The following example shows how an HTTP stub contract: + +==== +[source,groovy,indent=0,role="primary"] +.groovy +---- +package contracts + +org.springframework.cloud.contract.spec.Contract.make { + request { + method 'PUT' + url '/fraudcheck' + body([ + "client.id": $(regex('[0-9]{10}')), + loanAmount: 99999 + ]) + headers { + contentType('application/json') + } + } + response { + status OK() + body([ + fraudCheckStatus: "FRAUD", + "rejection.reason": "Amount too high" + ]) + headers { + contentType('application/json') + } + } +} +---- + +[source,yaml,indent=0,role="secondary"] +.yaml +---- +request: + method: PUT + url: /fraudcheck + body: + "client.id": 1234567890 + loanAmount: 99999 + headers: + Content-Type: application/json + matchers: + body: + - path: $.['client.id'] + type: by_regex + value: "[0-9]{10}" +response: + status: 200 + body: + fraudCheckStatus: "FRAUD" + "rejection.reason": "Amount too high" + headers: + Content-Type: application/json;charset=UTF-8 +---- +==== + +In the case of messaging, you can define: + +* The input and the output messages can be defined (taking into account from and where it +was sent, the message body, and the header). +* The methods that should be called after the message is received. +* The methods that, when called, should trigger a message. + +The following example shows a Camel messaging contract: + +==== +[source,groovy,indent=0,role="primary"] +.groovy +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] +---- + +[source,yaml,indent=0,role="secondary"] +.yaml +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] +---- +==== + +Running `./mvnw clean install` automatically generates tests that verify the application +compliance with the added contracts. By default, the generated tests are under +`org.springframework.cloud.contract.verifier.tests.`. + +The generated tests will differ, depending on which framework and test type you've setup in your plugin. + +In the samples below you'll find: + +- the default test mode for HTTP contracts via `MockMvc` +- JAX-RS client via the `JAXRS` test mode +- `WebTestClient` based test (this is particularly recommended while working with Reactive, `Web-Flux`-based applications) set via `WEBTESTCLIENT` test mode +- Spock based test via the `testFramework` property set to `SPOCK` + +==== +[source,java,indent=0,role="primary"] +.mockmvc +---- +@Test +public void validate_shouldMarkClientAsFraud() throws Exception { + // given: + MockMvcRequestSpecification request = given() + .header("Content-Type", "application/vnd.fraud.v1+json") + .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); + + // when: + ResponseOptions response = given().spec(request) + .put("/fraudcheck"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); + assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); +} +---- + +[source,java,indent=0,role="secondary"] +.jaxrs +---- +@SuppressWarnings("rawtypes") +public class FooTest { + WebTarget webTarget; + + @Test + public void validate_() throws Exception { + + // when: + Response response = webTarget + .path("/users") + .queryParam("limit", "10") + .queryParam("offset", "20") + .queryParam("filter", "email") + .queryParam("sort", "name") + .queryParam("search", "55") + .queryParam("age", "99") + .queryParam("name", "Denis.Stepanov") + .queryParam("email", "bob@email.com") + .request() + .build("GET") + .invoke(); + String responseAsString = response.readEntity(String.class); + + // then: + assertThat(response.getStatus()).isEqualTo(200); + + // and: + DocumentContext parsedJson = JsonPath.parse(responseAsString); + assertThatJson(parsedJson).field("['property1']").isEqualTo("a"); + } + +} +---- + +[source,java,indent=0,role="secondary"] +.webtestclient +---- +@Test + public void validate_shouldRejectABeerIfTooYoung() throws Exception { + // given: + WebTestClientRequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"age\":10}"); + + // when: + WebTestClientResponse response = given().spec(request) + .post("/check"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK"); + } +---- + +[source,groovy,indent=0,role="secondary"] +.spock +---- +given: + ContractVerifierMessage inputMessage = contractVerifierMessaging.create( + \'\'\'{"bookName":"foo"}\'\'\', + ['sample': 'header'] + ) + +when: + contractVerifierMessaging.send(inputMessage, 'jms:delete') + +then: + noExceptionThrown() + bookWasDeleted() +---- +==== + +As the implementation of the functionalities described by the contracts is not yet +present, the tests fail. + +To make them pass, you must add the correct implementation of handling either HTTP +requests or messages. Also, you must add a correct base test class for auto-generated +tests to the project. This class is extended by all the auto-generated tests and should +contain all the setup necessary to run them (for example, `RestAssuredMockMvc` controller +setup or messaging test setup). + +Once the implementation and the test base class are in place, the tests pass, and both the +application and the stub artifacts are built and installed in the local Maven repository. +Information about installing the stubs jar to the local repository appears in the logs, as +shown in the following example: + +[source,bash,indent=0] +---- + [INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- + [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar + [INFO] + [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- + [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar + [INFO] + [INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- + [INFO] + [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- + [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar + [INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom + [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar +---- + +You can now merge the changes and publish both the application and the stub artifacts +in an online repository. + +[[getting-started-first-application-consumer]] +=== On the Consumer Side + +`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running +WireMock instance or messaging route that simulates the actual service. + +To get started, add the dependency to `Spring Cloud Contract Stub Runner`: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] +---- + +You can get the Producer-side stubs installed in your Maven repository in either of two +ways: + +* By checking out the Producer side repository and adding contracts and generating the +stubs by running the following commands: ++ +[source,bash,indent=0] +---- +$ cd local-http-server-repo +$ ./mvnw clean install -DskipTests +---- +NOTE: The tests are skipped because the Producer-side contract implementation is not yet +in place, so the automatically-generated contract tests fail. +* Getting already existing producer service stubs from a remote repository. To do so, +pass the stub artifact IDs and artifact repository URl as `Spring Cloud Contract Stub +Runner` properties, as shown in the following example: ++ +[source,yaml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +---- + +Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, +provide the `group-id` and `artifact-id` for `Spring Cloud Contract Stub Runner` to run +the collaborators' stubs for you, as shown in the following example: + +[source,java, indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.NONE) +@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, + stubsMode = StubRunnerProperties.StubsMode.LOCAL) +public class LoanApplicationServiceTests { +---- + +TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and +`LOCAL` for offline work. + +In your integration test, you can receive stubbed versions of HTTP responses or messages +that are expected to be emitted by the collaborator service. You can see entries similar +to the following in the build logs: + +[source,bash,indent=0] +---- +2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version +2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT +2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] +2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar +2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] +2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] +2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] +---- + +[[getting-started-cdc]] +== Step-by-step Guide to Consumer Driven Contracts (CDC) with contracts laying on the producer side + +Consider an example of Fraud Detection and the Loan Issuance process. The business +scenario is such that we want to issue loans to people but do not want them to steal from +us. The current implementation of our system grants loans to everybody. + +Assume that `Loan Issuance` is a client to the `Fraud Detection` server. In the current +sprint, we must develop a new feature: if a client wants to borrow too much money, then +we mark the client as a fraud. + +Technical remarks + +* Fraud Detection has an `artifact-id` of `http-server`, +* Loan +Issuance has an artifact-id of `http-client` +* both have a `group-id` of `com.example` +* For the sake of this example the `Stub Storage` will be Nexus / Artifactory + +Social remarks + +* both client and server development teams need to communicate directly and +discuss changes while going through the process +* CDC is all about communication. + +The https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-server[server +side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-client[the +client code here]. + +TIP: In this case, the producer owns the contracts. Physically, all the contract are +in the producer's repository. + +[[getting-started-cdc-technical-note]] +=== Technical note + +If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the +following section to your build: + +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repos,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0] +---- + +For simplicity we will use the following acronyms: + +- Loan Issuance - LI - the http client +- Fraud Detection - FD - the http server +- Spring Cloud Contract - SCC + +=== Consumer side (Loan Issuance) + +As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps: + +. Start doing TDD by writing a test for your feature. +. Write the missing implementation. +. Clone the Fraud Detection service repository locally. +. Define the contract locally in the repo of Fraud Detection service. +. Add the Spring Cloud Contract (SCC) plugin. +. Run the integration tests. +. File a pull request. +. Create an initial implementation. +. Take over the pull request. +. Write the missing implementation. +. Deploy your app. +. Work online. + +Let's start with the Loan Issuance flow: + +[plantuml, getting-started-cdc-client, png] +---- +"Loan\nIssuance"->"Loan\nIssuance": start doing TDD\nby writing a test\nfor your feature +"Loan\nIssuance"->"Loan\nIssuance": write the \nmissing implementation +"Loan\nIssuance"->"Loan\nIssuance": run a test - it fails\ndue to no server running +"Loan\nIssuance"->"Fraud\nDetection\nClone": clone the repository +"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add missing dependencies\n& define contracts +"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add the SCC plugin +"Fraud\nDetection\nClone"->"FD \nClone Build": install the stubs locally +"FD \nClone Build"->"SCC Plugin \nin FD Clone": generate stubs \nand stubs \nartifact (e.g. stubs-jar) +"SCC Plugin \nin FD Clone"->"FD \nClone Build": stubs and artifacts\ngenerated +"FD \nClone Build"->"Local storage": install the stubs locally +"Local storage"->"FD \nClone Build": stub sucessfully installed +"FD \nClone Build"->"Fraud\nDetection\nClone": build successful +"Loan\nIssuance"->"Loan\nIssuance": add a SCC\nStub Runner\ndependency\nand setup +"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nlocal storage +"LI\nSCC\nStub Runner"->"Local storage": find stubs of [FD] +"Local storage"->"LI\nSCC\nStub Runner": stubs of [FD] found +"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] +"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running +"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test +"Loan\nIssuance"->"Loan\nIssuance": run a test +"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub +"FD stub"->"Loan\nIssuance": stub responds successfuly +"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully +"Loan\nIssuance"->"Fraud\nDetection": send a pull request\nwith the\nsuggested contracts +---- + +*Start doing TDD by writing a test for your feature.* + +[source,groovy,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0] +---- + +Assume that you have written a test of your new feature. If a loan application for a big +amount is received, the system should reject that loan application with some description. + +*Write the missing implementation.* + +At some point in time, you need to send a request to the Fraud Detection service. Assume +that you need to send the request containing the ID of the client and the amount the +client wants to borrow. You want to send it to the `/fraudcheck` url via the `PUT` method. + +[source,groovy,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] +---- + +For simplicity, the port of the Fraud Detection service is set to `8080`, and the +application runs on `8090`. + +If you start the test at this point, it breaks, because no service currently runs on port +`8080`. + +*Clone the Fraud Detection service repository locally.* + +You can start by playing around with the server side contract. To do so, you must first +clone it. + +[source,bash,indent=0] +---- +$ git clone https://your-git-server.com/server-side.git local-http-server-repo +---- + +*Define the contract locally in the repo of Fraud Detection service.* + +As a consumer, you need to define what exactly you want to achieve. You need to formulate +your expectations. To do so, write the following contract: + +IMPORTANT: Place the contract under `src/test/resources/contracts/fraud` folder. The `fraud` folder +is important because the producer's test base class name references that folder. + +==== +[source,groovy,indent=0,role="primary"] +.groovy +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] +---- + +[source,yaml,indent=0,role="secondary"] +.yaml +---- +include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] +---- +==== + +The YML contract is quite straight-forward. However when you take a look at the Contract +written using a statically typed Groovy DSL - you might wonder what the +`value(client(...), server(...))` parts are. By using this notation, Spring Cloud +Contract lets you define parts of a JSON block, a URL, etc., which are dynamic. In case +of an identifier or a timestamp, you need not hardcode a value. You want to allow some +different ranges of values. To enable ranges of values, you can set regular expressions +matching those values for the consumer side. You can provide the body by means of either +a map notation or String with interpolations. +//TODO: Ensure that the links are correct +Consult the <> section for more information. We highly recommend using the map notation! + +TIP: You must understand the map notation in order to set up contracts. Please read the +https://groovy-lang.org/json.html[Groovy docs regarding JSON]. + +The previously shown contract is an agreement between two sides that: + +- if an HTTP request is sent with all of +** a `PUT` method on the `/fraudcheck` endpoint, +** a JSON body with a `client.id` that matches the regular expression `[0-9]{10}` and +`loanAmount` equal to `99999`, +** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`, +- then an HTTP response is sent to the consumer that +** has status `200`, +** contains a JSON body with the `fraudCheckStatus` field containing a value `FRAUD` and +the `rejectionReason` field having value `Amount too high`, +** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`. + +Once you are ready to check the API in practice in the integration tests, you need to +install the stubs locally. + +*Add the Spring Cloud Contract Verifier plugin.* + +We can add either a Maven or a Gradle plugin. In this example, you see how to add Maven. +First, add the `Spring Cloud Contract` BOM. + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_bom,indent=0] +---- + +Next, add the `Spring Cloud Contract Verifier` Maven plugin + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] +---- + +Since the plugin was added, you get the `Spring Cloud Contract Verifier` features which, +from the provided contracts: + +- generate and run tests +- produce and install stubs + +You do not want to generate tests since you, as the consumer, want only to play with the +stubs. You need to skip the test generation and execution. When you execute: + +[source,bash,indent=0] +---- +$ cd local-http-server-repo +$ ./mvnw clean install -DskipTests +---- + +In the logs, you see something like this: + +[source,bash,indent=0] +---- +[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- +[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar +[INFO] +[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- +[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar +[INFO] +[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- +[INFO] +[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- +[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar +[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom +[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar +---- + +The following line is extremely important: + +[source,bash,indent=0] +---- +[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar +---- + +It confirms that the stubs of the `http-server` have been installed in the local +repository. + +*Run the integration tests.* + +In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic +stub downloading, you must do the following in your consumer side project (`Loan +Application service`): + +Add the `Spring Cloud Contract` BOM: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=contract_bom,indent=0] +---- + +Add the dependency to `Spring Cloud Contract Stub Runner`: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] +---- + +Annotate your test class with `@AutoConfigureStubRunner`. In the annotation, provide the +`group-id` and `artifact-id` for the Stub Runner to download the stubs of your +collaborators. (Optional step) Because you're playing with the collaborators offline, you +can also provide the offline work switch (`StubRunnerProperties.StubsMode.LOCAL`). + +[source,groovy,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] +---- + +Now, when you run your tests, you see something like this: + +[source,bash,indent=0] +---- +2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version +2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT +2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] +2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar +2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] +2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] +2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] +---- + +This output means that Stub Runner has found your stubs and started a server for your app +with group id `com.example`, artifact id `http-server` with version `0.0.1-SNAPSHOT` of +the stubs and with `stubs` classifier on port `8080`. + +*File a pull request.* + +What you have done until now is an iterative process. You can play around with the +contract, install it locally, and work on the consumer side until the contract works as +you wish. + +Once you are satisfied with the results and the test passes, publish a pull request to +the server side. Currently, the consumer side work is done. + +=== Producer side (Fraud Detection server) + +As a developer of the Fraud Detection server (a server to the Loan Issuance service) we would like to: + +- take over the pull request +- write the missing implementation +- deploy the application + +Let's look at the Fraud Detection flow: + +[plantuml, getting-started-cdc-server, png] +---- +"Fraud\nDetection"->"Fraud\nDetection": take over the\n pull request +"Fraud\nDetection"->"Fraud\nDetection": setup\nSpring Cloud\nContract plugin +"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build +"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) +"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated +"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests +"Fraud\nDetection\nBuild"->"Fraud\nDetection": generated tests failed! +"Fraud\nDetection"->"Fraud\nDetection": setup\nbase classes\nfor contract tests +"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build +"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) +"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated +"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests +"Fraud\nDetection\nBuild"->"Fraud\nDetection": all the tests passed! +"Fraud\nDetection"->"Fraud\nDetection": commit and push changes +"Fraud\nDetection"->"CI": commit pushed!\nTriggers the build +"CI"->"Stub Storage": build successful,\nupload artifacts +---- + +*Take over the pull request.* + +As a reminder, you can see the initial implementation here: + +[source,java,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] +include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] +} +---- + +[source,bash,indent=0] +---- +$ git checkout -b contract-change-pr master +$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr +---- + +You must add the dependencies needed by the autogenerated tests: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] +---- + +In the configuration of the Maven plugin, pass the `packageWithBaseClasses` property + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] +---- + +IMPORTANT: This example uses "convention based" naming by setting the +`packageWithBaseClasses` property. Doing so means that the two last packages combine to +make the name of the base test class. In our case, the contracts were placed under +`src/test/resources/contracts/fraud`. Since you do not have two packages starting from +the `contracts` folder, pick only one, which should be `fraud`. Add the `Base` suffix and +capitalize `fraud`. That gives you the `FraudBase` test class name. + +All the generated tests extend that class. Over there, you can set up your Spring Context +or whatever is necessary. In this case, use https://github.com/rest-assured/rest-assured[Rest Assured MVC] to +start the server side `FraudDetectionController`. + +[source,java,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/test/java/com/example/fraud/FraudBase.java[] +---- + +Now, if you run the `./mvnw clean install`, you get something like this: + +[source,bash,indent=0] +---- +Results : + +Tests in error: + ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed... +---- + +This error occurs because you have a new contract from which a test was generated and it +failed since you have not implemented the feature. The auto-generated test would look +like this: + +[source,java,indent=0] +---- +@Test +public void validate_shouldMarkClientAsFraud() throws Exception { + // given: + MockMvcRequestSpecification request = given() + .header("Content-Type", "application/vnd.fraud.v1+json") + .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); + + // when: + ResponseOptions response = given().spec(request) + .put("/fraudcheck"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); + assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); +} +---- + +If you used the Groovy DSL, you can see, all the `producer()` parts of the Contract that were present in the +`value(consumer(...), producer(...))` blocks got injected into the test. +In case of using YAML, the same applied for the `matchers` sections of the `response`. + +Note that, on the producer side, you are also doing TDD. The expectations are expressed +in the form of a test. This test sends a request to our own application with the URL, +headers, and body defined in the contract. It also is expecting precisely defined values +in the response. In other words, you have the `red` part of `red`, `green`, and +`refactor`. It is time to convert the `red` into the `green`. + +*Write the missing implementation.* + +Because you know the expected input and expected output, you can write the missing +implementation: + +[source,java,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] +include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0] +include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] +} +---- + +When you execute `./mvnw clean install` again, the tests pass. Since the `Spring Cloud +Contract Verifier` plugin adds the tests to the `generated-test-sources`, you can +actually run those tests from your IDE. + +*Deploy your app.* + +Once you finish your work, you can deploy your change. First, merge the branch: + +[source,bash,indent=0] +---- +$ git checkout master +$ git merge --no-ff contract-change-pr +$ git push origin master +---- + +Your CI might run something like `./mvnw clean deploy`, which would publish both the +application and the stub artifacts. + +=== Consumer Side (Loan Issuance) Final Step + +As a developer of the Loan Issuance service (a consumer of the Fraud Detection server) we will want to: + +- merge our feature branch to `master` +- switch to online mode of working + + +[plantuml, getting-started-cdc-client, png] +---- +"Loan\nIssuance"->"Loan\nIssuance": merge the\nfeature branch\nto master branch +"Loan\nIssuance"->"Loan\nIssuance": setup SCC Stub Runner\nto fetch stubs\nfrom Stub Storage +"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nStub Storage +"LI\nSCC\nStub Runner"->"Stub Storage": find stubs of [FD] +"Stub Storage"->"LI\nSCC\nStub Runner": stubs of [FD] found +"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] +"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running +"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test +"Loan\nIssuance"->"Loan\nIssuance": run a test +"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub +"FD stub"->"Loan\nIssuance": stub responds successfuly +"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully +---- + +*Merge branch to master.* + +[source,bash,indent=0] +---- +$ git checkout master +$ git merge --no-ff contract-change-pr +---- + +*Work online.* + +Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate +where the repository with your stubs is located. At this moment the stubs of the server +side are automatically downloaded from Nexus/Artifactory. You can set the value of +`stubsMode` to `REMOTE`. The following code shows an example of +achieving the same thing by changing the properties. + +[source,yaml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +---- + +That's it! + +[[getting-started-whats-next]] +== What to Read Next +Hopefully, this section provided some of the {project-full-name} basics and got you on your way +to writing your own applications. If you are a task-oriented type of developer, you might +want to jump over to https://spring.io and check out some of the +https://spring.io/guides/[getting started] guides that solve specific "`How do I do that +with Spring?`" problems. We also have {project-full-name}-specific +"`<>`" reference documentation. + +Otherwise, the next logical step is to read _<>_. If +you are really impatient, you could also jump ahead and read about +_<>_. + +// TODO: Check if we can link courses +In addition to that you can check out the following videos: + +- "Consumer Driven Contracts and Your Microservice Architecture" by Olga Maciaszek-Sharma and Marcin Grzejszczak + +video::pDkC_00hhvA[youtube,width=640,height=480] + +- "Contract Tests in the Enterprise" by Marcin Grzejszczak + +video::ZyHG-VOzPZg[youtube,width=640,height=480] + +- "Why Contract Tests Matter?" by Marcin Grzejszczak + +video::TvpkZu1e2Dc[youtube,start=6262,width=640,height=480] + +You can find the default project samples at +https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. + +You can find the Spring Cloud Contract workshops https://cloud-samples.spring.io/spring-cloud-contract-samples/[over here]. \ No newline at end of file