Defining Spring Cloud Contracts in Open API

Defining Spring Cloud Contracts in Open API

6 Comments

Working for startups is always an interesting experience. Currently, I’m a software engineer at Velo Payments. If you’ve ever worked for a startup, you’ll quickly see that you get to wear many hats.

One of the hats I get to wear is the creation of developer center (currently in the oven). In the very near future, Velo will be exposing a set of financial APIs to move money around the world.

Within the developer center, we hope to be introducing hundreds, thousands of consumers to our APIs.

API development is never an easy task. And evolving APIs is even more complicated.

Our use case raises a number of concerns:

  • How can we ensure we don’t inadvertently release a breaking change to our APIs?
  • How do we communicate how to use our APIs?
  • How do we document our APIs?
  • How do we automate testing of our APIs?
  • Can we do all this and remain technology agnostic?

There is a plethora of tools available for our use. Yet none is ‘just right’.

We clearly have a use case for Consumer Driven Contracts. To summarize the folks at ThoughtWorks:

Consumer-Driven Contracts are a pattern for evolving services. In Consumer-Driven Contracts, each consumer captures their expectations of the provider in a separate contract. All of these contracts are shared with the provider so they gain insight into the obligations they must fulfill for each individual client. The provider can create a test suite to validate these obligations. This lets them stay agile and make changes that do not affect any consumer, and pinpoint consumers that will be affected by a required change for deeper planning and discussion.

In a nutshell, a ‘contract’ can be looked at as a request/response pair. You give the API x, and can expect the API to return y. Contracts are a technique for defining API interactions.

Contracts, however, do a very poor job of documenting APIs.

For our use case of releasing public APIs, we want a technology agnostic method of documenting our APIs. Currently, Open API is a clear leader in this domain.

In 2015, SmartBear donated the Swagger 2.0 specification to the Open API Initiative. This kicked off the formation of the Open API Initiative, a consortium of companies, including 3Scale, Apigee, Capital One, Google, IBM, Intuit, Microsoft, PayPal, and Restlet.

In the summer of 2017, the Open API Initiative released the Open API 3.0 Specification. (Say adios to the name ‘Swagger’)

Open API 3.0 specifications can be written in JSON or YAML, and do an excellent job of documenting RESTful APIs.

The Open API Specification does not however, define API interactions.

The Open API 3.0 Specification does however, define extensions.

Through the use of Open API Specification Extensions, we can define Consumer Driven Contracts.

In this post I’m going to show you how to you can define Consumer Driven Contracts in the Open API 3.0 Specification for Spring Cloud Contract.

If you are not familiar with Spring Cloud Contract, please see my post showing how to use Spring Cloud Contract.

Spring Cloud Contract Groovy DSL

org.springframework.clo:ud.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')
		}
	}
}

One of my initial concerns of Spring Cloud Contract was how you need to define the contracts in Groovy, and in a very Spring specific Groovy DSL. It’s not something that would be portable to other technologies.

Spring Cloud Contract YAML DSL

Here is the same contract expressed in 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

Better. I like YAML, since its technology agnostic. Someone could port this DSL to a different technology stack.

Other Concerns about Spring Cloud Contract DSLs

Don’t Repeat Yourself

As Java developers, roughly since we learned how to write “Hello world”, Don’t repeat yourself, aka ‘DRY’ has been beaten in to our heads.

don't repeat yourselfLet’s say you have several conditions you wish to test for an endpoint. You’ll be duplicating a lot of code. Elements like the URL and Content-type will get repeated over and over. Clearly violating DRY!

And if you documented your API using Open API or Swagger, the DRY validations get even worse!

Consider the Spring Cloud Contract will define for each contract things like:

Spring Cloud Contract

  • Request / Response pairs
  • Paths
  • Parameters
  • Headers
  • Cookies
  • HTTP Methods
  • HTTP Status verbs

While the Open API Specification defines:

Open API

  • Paths
  • Parameters
  • Headers
  • Cookies
  • HTTP Methods
  • HTTP Status verbs
  • Request Schemas
  • Response Schemas

Consider the overlap:

Spring Cloud Contract / Open API

  • Paths
  • Parameters
  • Headers
  • Cookies
  • HTTP Methods
  • HTTP Status verbs
  • Request / Response Objects

Now we have DRY violations stacking up like flights going into Chicago O’hare!

What if I wish to refactor a URL path? Now I’m updating the controller source code, tests, contracts, and API documentation.

Thank god our IDEs have search and replace capabilities!

You Can’t Handle the Truth!

In my use case, the APIs under development will be public.

Thus, we do need solid API documentation. It does not need to be Open API. But it does need to be some type of friendly, human-readable documentation.

As you start to define API attributes in contracts and in API documentation the question starts to become “what is the single source of truth for the API?”

One could argue it should be the API documentation.

Yet, just as easy to say it should be the consumer-driven contracts.

Who’s API is it Anyways?

If we can’t determine the single source of truth for the API, who is the owner of the API?

Do the consumer-driven contracts own the API? So the API documentation needs to conform to the contracts when there is a difference?

Or is the API definitively defined by the documentation? Thus, contracts must adhere to the API documentation.

Again a situation where valid arguments can be made for either one.

Contract First vs Code First vs Document First

Do you write contracts first?

Do you code first?

Do you write API documentation first?

We’re mostly developers, so code first, right???

What if we could write the API specification and contracts at the same time?

I know this whole area is subject to some very spirited debate. Not something I’ll be able to solve in this post.

Personally, I’m leaning more and more towards having the specification first, then contracts, then code.

Yes, there is tooling to generate Swagger / Open API from the Spring Source code. My biggest hesitation there is how do you prevent inadvertent breaking changes? Since your specification is generated from the source code, it will always be right. Even after you’ve broken a consumer.

Spring Cloud Contract Open API 3.0 Contract Converter

It is actually now possible to write Spring Cloud Contract definitions using Open API 3.0 with my Spring Cloud Contract Open API Contract Converter or SCC OA3 Converter for short.

Having the API Specification and API documentation in a single document addresses many of the concerns above.

    • DRY violations are minimized!
  • A single source of truth for the API
  • The API is defined by the API Specification
  • Clear ownership of what the API is!

In a nutshell, the SCC OA3 Converter combines the SCC YAML DSL into OA3 extensions.

From the SCC OA3 Converter, you can expect:

  • Near 100% compatibility to the SCC YAML DSL (still testing edge cases)
  • The ability to define multiple contracts in OA3
  • Minimal violations of DRY
  • Having a single document which defines your API
  • The resulting OA3 Specification is 100% compatible with other OA3 tooling.

Open API 3.0 Consumer Driven Contracts Example

Spring Cloud Contract YAML Definitions

First lets explore two contracts written using the existing YAML DSL of Spring Cloud Contract.

These two examples are from the YAML samples available in the Spring Cloud Contract GitHub Repository. I’m leaving the commenting in to help explain what each contract is doing.

Contract 1 – Should Mark Client as Fraud

request: # (1)
  method: PUT # (2)
  url: /fraudcheck # (3)
  body: # (4)
    "client.id": 1234567890
    loanAmount: 99999
  headers: # (5)
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id'] # (6)
        type: by_regex
        value: "[0-9]{10}"
response: # (7)
  status: 200 # (8)
  body:  # (9)
    fraudCheckStatus: "FRAUD"
    "rejection.reason": "Amount too high"
  headers: # (10)
    Content-Type: application/json;charset=UTF-8


#From the Consumer perspective, when shooting a request in the integration test:
#
#(1) - If the consumer sends a request
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
#(7) - then the response will be sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`
#
#From the Producer perspective, in the autogenerated producer-side test:
#
#(1) - A request will be sent to the producer
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json;charset=UTF-8`

Contract 2 – Should Mark Client as Not Fraud

request:
  method: PUT
  url: /fraudcheck
  body:
    "client.id": 1234567890
    loanAmount: 123.123
  headers:
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id']
        type: by_regex
        value: "[0-9]{10}"
response:
  status: 200
  body:
    fraudCheckStatus: "OK"
    "rejection.reason": null
  headers:
    Content-Type: application/json;charset=UTF-8
  matchers:
    body:
      - path: $.['rejection.reason']
        type: by_command
        value: assertThatRejectionReasonIsNull($it)

It’s fairly clear what these two contracts are testing for.

Spring Cloud Contract Open API 3.0 Contracts

Here are the same contracts expressed using the Open API Specification.

Following the spirit of DRY, contract elements which can be derived from the Open API Specification, such as PATH are.

While elements which relate to defining the API interaction are defined in Open API Extensions.

Any property which starts with an x- is an Open API Extension object. As much as possible, the extension objects are modeled after the Spring Cloud Contract YAML DSL.

Open API 3.0 Contracts Example

This is the complete example. Following this example, I’ll break things down in depth.

openapi: 3.0.0
info:
    description: Spring Cloud Contract Verifier Http Server OA3 Sample
    version: "1.0.0"
    title: Fraud Service API
paths:
    /fraudcheck:
        put:
            summary: Perform Fraud Check
            x-contracts:
            - contractId: 1
              name: Should Mark Client as Fraud
              priority: 1
            - contractId: 2
              name: Should Not Mark Client as Fraud
            requestBody:
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                "client.id":
                                    type: integer
                                loanAmount:
                                    type: integer
                x-contracts:
                - contractId: 1
                  body:
                      "client.id": 1234567890
                      loanAmount: 99999
                  matchers:
                      body:
                      - path: $.['client.id']
                        type: by_regex
                        value: "[0-9]{10}"
                - contractId: 2
                  body:
                      "client.id": 1234567890
                      loanAmount: 123.123
                  matchers:
                      body:
                      - path: $.['client.id']
                        type: by_regex
                        value: "[0-9]{10}"
            responses:
                '200':
                    description: created ok
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    fraudCheckStatus:
                                        type: string
                                    "rejection.reason":
                                        type: string
                    x-contracts:
                    - contractId: 1
                      body:
                          fraudCheckStatus: "FRAUD"
                          "rejection.reason": "Amount too high"
                      headers:
                          Content-Type: application/json;charset=UTF-8
                    - contractId: 2
                      body:
                          fraudCheckStatus: "OK"
                          "rejection.reason": null
                      headers:
                          Content-Type: application/json;charset=UTF-8
                      matchers:
                          body:
                          - path: $.['rejection.reason']
                            type: by_command
                            value: assertThatRejectionReasonIsNull($it)

Let’s break things down on how the contracts are defined in the Open API Specification.

Contract Definition

At a high level, contracts are defined using an extension on the Open API Operation Object.

In this snippet, I’m defining two contracts.

Open API Snippet

paths:
    /fraudcheck:
        put:
            summary: Perform Fraud Check
            x-contracts:
            - contractId: 1
              name: Should Mark Client as Fraud
              priority: 1
            - contractId: 2
              name: Should Not Mark Client as Fraud

Both contracts will be applied against the path /fraudcheck and the HTTP verb PUT.

The extension object x-contracts is a list. The objects in the list are expected to have a contract ID. This ID property is important since it allows us to tie together properties of the contract defined in other sections of the Open API Specification.

Contract Request Definition

To define the request of the contract, the Open API Request Body Object is extended.

In this snippet, you can see how the Request Body is extended.

From the Open API Specification, we can determine the request should use application/json for Content Type.

Then under the x-contracts property, the request properties for two contracts are defined.

Open API Snippet

            requestBody:
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                "client.id":
                                    type: integer
                                loanAmount:
                                    type: integer
                x-contracts:
                - contractId: 1
                  body:
                      "client.id": 1234567890
                      loanAmount: 99999
                  matchers:
                      body:
                      - path: $.['client.id']
                        type: by_regex
                        value: "[0-9]{10}"
                - contractId: 2
                  body:
                      "client.id": 1234567890
                      loanAmount: 123.123
                  matchers:
                      body:
                      - path: $.['client.id']
                        type: by_regex
                        value: "[0-9]{10}"

Contrast the above to this snippet from the Spring Cloud Contract YAML DSL.

Spring Cloud Contract YAML DSL Snippet

  body:
    "client.id": 1234567890
    loanAmount: 123.123
  headers:
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id']
        type: by_regex
        value: "[0-9]{10}"

The body and matchers elements are the same.

While Content Type is not needed, since it is derived from the Open API Specification.

Contract Response Definition

To define the expected response for a given contract, the Open API Response Object is extended.

In the snippet below, the Open API Response object is the 200 YAML property.

From the Open API properties, we can infer the expected response should have an HTTP status of 200, and the expected content type is application/json.

The response object is extended with the x-contracts property.

In this example, you can see the expected response properties defined for two contracts.

Open API Snippet

            responses:
                '200':
                    description: created ok
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    fraudCheckStatus:
                                        type: string
                                    "rejection.reason":
                                        type: string
                    x-contracts:
                    - contractId: 1
                      body:
                          fraudCheckStatus: "FRAUD"
                          "rejection.reason": "Amount too high"
                      headers:
                          Content-Type: application/json;charset=UTF-8
                    - contractId: 2
                      body:
                          fraudCheckStatus: "OK"
                          "rejection.reason": null
                      headers:
                          Content-Type: application/json;charset=UTF-8
                      matchers:
                          body:
                          - path: $.['rejection.reason']
                            type: by_command
                            value: assertThatRejectionReasonIsNull($it)

Again, let’s contrast this against the original Spring Cloud Contract YAML DSL example.

Here you can see we’re are expecting an HTTP 200 status and content type of application/json. (both defined in Open API Specification properties above)

And again the body and matchers elements remain the same.

Spring Cloud Contract YAML DSL Snippet

response:
  status: 200
  body:
    fraudCheckStatus: "OK"
    "rejection.reason": null
  headers:
    Content-Type: application/json;charset=UTF-8
  matchers:
    body:
      - path: $.['rejection.reason']
        type: by_command
        value: assertThatRejectionReasonIsNull($it)

Next Steps

How to Define Your Own Contracts in Open API 3.0

If you’d like to try defining your own contracts for Spring Cloud Contract, please see my GitHub Repository. Here you will find complete directions on how to configure Maven and additional examples.

The above examples reference a common example used in the Spring Cloud Contract stand-alone examples. You can find a complete example of a stand-alone reference project here in GitHub. In this example, I literally copied the Java classes used in the Spring Cloud Contract YAML example, deleted the YAML contracts, and re-wrote them in Open API 3.0.

Help Wanted!

My Open API Contract converter is in its initial release. Spring Cloud Contract has a variety of examples of YAML contracts in their unit tests. I’d like to convert the remaining YAML contracts to Open API 3.0 contracts and write unit test for them. This is an area I’d love to get some help with.

If you’d like to contribute to this project, please see open issues here. I’ve also setup a Gitter room where you can communicate with me and others contributing to the project.

Atlassian’s Swagger Request Validator

Another tool I wish to explore is Atlassian’s Swagger Request Validator. They’ve added support for the Open API 3.0 specification just in the last few weeks. I want to see what additional assertions can be automated from properties defined in the API specification.

API Documentation for Humans

The Open API examples we’ve been looking at in this post are in YAML. YAML is great for computers, but not so great for humans.

The folks from Rebilly have open sourced their API documentation. They have a parser which consumes the Open API YAML to produce very rich API documentation using ReactJS. You can see an example here. I’m currently looking at using this tool document Velo’s public APIs.

Special Thanks

Special Thanks to Marcin Grzejszczak, one of the primary authors of Spring Cloud Contract. He’s been very helpful with Spring Cloud contract in general, and in guiding me in how to write the Open API 3.0 contract parser.

In Summary

Developing quality APIs is challenging. For the public API’s I’m supporting, using the Open API specification was an easy choice.

If I can provide an Open API specification of my APIs to others, now they have a tool they can leverage. I don’t know if my API consumers will be using Spring, .NET, Python, Ruby, or whatever.

Due to the popularity of Open API and Swagger, there are a ton of tools to choose from.

Using the Open API Specification, I can:

  • Generate Server Side and Client Side stubs in roughly a gazillion different languages.
  • Create documentation in markdown
  • Insert request / response samples.
  • Provide code samples
  • Auto-generate code for Pact, Wiremock, RestAssured, Spring MockMVC via the Atlassian tools mentioned above.
  • Interact with the APIs via Swagger UI
  • Generate rich friendly API documentation like this Rebilly example. (Rebilly is just one example, there are many others)
  • And much more.

Seems like you can do more and more with Open API. You can even get a validator badge for GitHub. (OA3 support coming soon)

And now, you can define Consumer Driven Contracts for Spring Cloud Contract in Open API 3.0!

About jt

    You May Also Like

    6 comments on “Defining Spring Cloud Contracts in Open API

    1. August 23, 2018 at 5:26 am

      Why spring 5 course removed in udemy .

      Reply
      • August 23, 2018 at 9:03 am

        Misunderstanding with Udemy. Working with them now, hopefully resolved soon.

        Reply
      • August 23, 2018 at 2:38 pm

        Should be back to normal now! Enjoy!

        Reply
    2. March 23, 2021 at 7:22 am

      Very well explained.
      I have one doubt here – > If I have understood correctly you have created the Contract yaml files inside ” src/main/test/resources/openapi ” folder manually ?

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.