- Maven
- Java 11 (earler versions are also OK)
- Spring Boot 2.4.5
- Spring Cloud Contract 3.0.2
- Lombok - you may need to install the Lombok plugin if using Intellij
- OpenApi - to generate the interfaces from YAML files
- OpenFeign - to generate client APIs from openApi YAML files
- Sleuth - to help with tracing
- Groovy – to write some of the tests
- Wiremock - to stub endpoints
- MongoDB - a Non-SQL (or non relational) database, which stores data in JSON like objects
- Flapdoodle – for testing with an in-memory mongodb database
- This demo does not use Kotlin
You may need to install a Lombok plugin to get this code working on your IDE
For more details see https://www.baeldung.com/intro-to-project-lombok
The micro-services use the Sleuth dependency. This dependency adds and forwards traceIds (and other fields) in request message. If you know the traceId, you can examine log files to see all relevant logs records. You can also link your services to tools like ZipKin This enables requests to be easily traced through a micro-service environment
For more details see: https://dzone.com/articles/monitoring-microservices-with-spring-cloud-sleuth
The Provider service makes use of a non-SQL database
For more details, see: https://www.baeldung.com/spring-data-mongodb-tutorial
See https://www.baeldung.com/spring-mock-mvc-rest-assured
See https://www.baeldung.com/introduction-to-wiremock
For more details about OpenApi, see https://www.baeldung.com/spring-rest-openapi-documentation
If you are using Intellij, you may need to mark the following directory as "Generated test sources"
- /target/generated-test-sources/contracts
The openApi yml files (product.yml, consiumer.yml and marketing.yml) are used to generate code that is used in the controllers and clients.
For example, in the Consumer service, mvn clean install will generate code in target/generated-sources/openapi/src/main And then
- ConsumerController implements ComsumerApi to handle the REST calls from the front-end
- ProductsService uses the generated bean ProviderApiClient to handle the REST calls to the provider service
- MarketingService uses the generated bean MarketingApiClient to handle the REST calls to the legacyt marketing service
Note the generated ApiClient code uses OpenFeign to process the REST calls
The test directrory structure is extremely important.
Directory names are used by spring-boot-contract to generate the names of Java classes that get generated from groovy files, and may be used to determine the names of base classs.
For example, in the provider service, we have the following (note in the Consumer, we directly specify the base class in the pom, so this is not relevant)
<packageWithBaseClasses>
nl.crook.olly.contract.demo.provider.contract.base
<packageWithBaseClasses>
Because the pom specifed the package containing base classes, Spring Boot Contract will expect the following for each directory:
- src/test/resources/contracts/providerContract -> expects to use ProviderContractBase.java
- src/test/resources/contracts/providerIntegration -> expects to use providerIntegrationBase.java
These classes must be present in the package nl.crook.olly.contract.demo.provider.contract.base
Groovy files get compiled to Java classes, with the Java names based on the directory name by default
For example, if you have
- src/test/resources/contracts/providerIntegration/test1.groovy
- src/test/resources/contracts/providerIntegration/test2.groovy
Then mvn clean install will generate a test class based on te directory name
- provider/target/generated-test-sources/contracts/nl/crook/olly/contract/demo/provider/contract/base/providerIntegration.java
And providerIntegration.java will contain the following 2 methods
- validate_test1()
- validate_test2()
You can override the default naming by using the "name" attribute in your groovy (or Java or Kotlin or YAML) contract.
For example, if test1.groovy contains
"name": "groovy contract"
Then providerIntegration.java will contain
- validate_GroovyContract()
You can run and debug providerIntegration.java just like any Junit test.
Note, you may have to mark the directory provider/target/generated-test-sources/contracts as Test Sources Root if using Intellij
I deliberately made an interface that could be misinterpreted, so that I could demonstrate a couple of points.
For example, in provider.yml, getProducts and getProductDetails both make use of the Products schema in their response messages
operationId: getProducts
response is List<Product>
operationId: getProductDetails
response is Product
This means there is a risk that anyone looking at the openApi definition might (incorrectly) assume that getProducts will return a list of Product, and that each item contains all the fields from Product.
However, getProducts fills only some of the fields of the Product (for example, it does not fill the description)
Ideally, we would not encounter this, but it is good to be aware of issues like this, and maybe consider refactoring the definition