introducing spring auto rest docs - spring io 2017
TRANSCRIPT
IntroducingSpring Auto REST Docs
Florian Benz@flbenz
@spring_io#springio17
DR REST DOCS
OR: HOW I LEARNED TO STOP WORRYING AND LOVE DOCUMENTATION
@spring_io#springio17
Florian Benz
Software Engineer
@flbenz
@spring_io#springio17
Scalable Capital
• Europe’s fastest growing Digital Wealth Manager
• Authorized financial institute in Germany and the UK
• From scratch with Spring Boot
• Joined effort with Juraj Misur @juraj_misur
@spring_io#springio17
Spring Auto REST Docs
Scalable Capital founded
Dec 2014
Proof of concept
Jul 2015
First article
Nov 2015
@spring_io#springio17
Spring Auto REST Docs
Open source&
First release
Dec 2016
DZone article
Jan 2017
Two releases with fixes and
features
Feb & Mar 2017
@spring_io#springio17
Our Story
@spring_io#springio17
Manual Documentation
DocumentationCode
@spring_io#springio17
A single big document
@spring_io#springio17
Specification-Driven Documentation
DocumentationCode
Specification
@spring_io#springio17
RAML Specification/weather:
get:
queryParameters:
city:
description: Name of a city in the given country.
responses:
200:
body:
application/json:
schema: |
{ "$schema": "http://json-schema.org/schema",
"type": "object",
"description": "Weather information",
"properties": {
"temperature": { "type": "number" }
}
}
@spring_io#springio17
Swagger / OpenAPI@GetMapping("weatherParam")@ApiOperation("weather")@ApiImplicitParams({ @ApiImplicitParam(name = "country", value = "Country code", required = true, dataType = "string", paramType = "query"), @ApiImplicitParam(name = "city", value = "City", required = true, dataType = "string", paramType = "query")})@ApiResponses({ @ApiResponse(code = 200, message = "Success", response = WeatherResponse.class)})public WeatherResponse weatherParam(@RequestParam @IsoCountryCode String country, @RequestParam String city) { return new WeatherResponse(20);}
@spring_io#springio17
Swagger / OpenAPI
@spring_io#springio17
Postman
@spring_io#springio17
Test-Driven Documentation
DocumentationCode
Tests
@spring_io#springio17
Spring REST Docs
Generatedsnippets
Tests
Hand-written documentation
Documentation
@spring_io#springio17
Spring MVC Test@Testpublic void shouldReturnWeatherForBarcelona() throws Exception { mockMvc.perform( post("/weather") .contentType(MediaType.APPLICATION_JSON) .content("{\"country\": \"ES\", \"city\": \"Barcelona\"}") ) .andExpect(status().isOk()) .andExpect(jsonPath("$.temperature", is(20)));}
@spring_io#springio17
Spring REST Docs@Testpublic void shouldReturnWeatherForBarcelona() throws Exception { mockMvc.perform( post("/weather") .contentType(MediaType.APPLICATION_JSON) .content("{\"country\": \"ES\", \"city\": \"Barcelona\"}") ) .andExpect(status().isOk()) .andExpect(jsonPath("$.temperature", is(20))); .andDo(document("weather", requestFields( fieldWithPath("country").description("Country code"), fieldWithPath("city").description("City name"))));}
@spring_io#springio17
Generated snippet|===|Path|Type|Optional|Description
|country|String|false|Country Code.
|city|false|true|City name.
|===
@spring_io#springio17
AsciiDoc[[resources-weather]]= Weather for your city
`POST /weather`
Up-to-date temperature for the given city
== Response structure
include::{snippets}/weather/response-fields.adoc[]
== Example request/response
include::{snippets}/weather/curl-request.adoc[]include::{snippets}/weather/http-response.adoc[]
@spring_io#springio17
Spring REST Docs
@spring_io#springio17
Spring REST Docs
@spring_io#springio17
Spring REST Docs
Controller
POJO
Response Entity
Jackson
HTTP response Documented
@spring_io#springio17
ExtendingSpring REST Docs
@spring_io#springio17
Motivation
.andDo(document("weather", requestFields( fieldWithPath("country").description("Country code"), fieldWithPath("city").description("Name of a city"))));
We are lazy
@spring_io#springio17
Proof of concept
@spring_io#springio17
Spring Auto REST Docs
Controller
POJO
Response Entity
Jackson
HTTP response
Javadoc
Introspection
@spring_io#springio17
Spring REST Docs@Testpublic void shouldReturnWeatherForBarcelona() throws Exception { mockMvc.perform( post("/weather") .contentType(MediaType.APPLICATION_JSON) .content("{\"country\": \"ES\", \"city\": \"Barcelona\"}") ) .andExpect(status().isOk()) .andExpect(jsonPath("$.temperature", is(20))); .andDo(document("weather", requestFields( fieldWithPath("country").optional().description("Country code"), fieldWithPath("city").optional().description("City name"))));}
@spring_io#springio17
Spring Auto REST Docs@Testpublic void shouldReturnWeatherForBarcelona() throws Exception { mockMvc.perform( post("/weather") .contentType(MediaType.APPLICATION_JSON) .content("{\"country\": \"ES\", \"city\": \"Barcelona\"}") ) .andExpect(status().isOk()) .andExpect(jsonPath("$.temperature", is(20))); .andDo(document("weather"));}
@spring_io#springio17
Javadocclass WeatherRequest { /** * Country code. */ private String country; /** * City name. */ private String city;}
Path Type Optional Description
country String true Country code.
city String true City name.
@spring_io#springio17
Constraintsclass WeatherRequest { /** * Country code, e.g. ES, DE, US. */ @NotNull @IsoCountryCode private String country; /** * City name. */ @NotBlank private String city;}
Path Type Optional Description
country String false Country code.Must be an ISO country code.
city String false City name.
@spring_io#springio17
Constraints
package.OneOf.description=Must be one of ${value}package.IsoCountryCode.description=Must be an ISO country code
ConstraintDesciptions.properties
@spring_io#springio17
Constraintsclass WeatherRequest { /** * Country code, e.g. ES, DE, US. */ @NotNull @IsoCountryCode(groups = Iso.class) @CountryName(groups = Plain.class) private String country;}
Path Type Optional Description
country String false Country code.ISO: Must be an ISO country code.Plain: Must be a country name.
@spring_io#springio17
Enumsclass WeatherRequest { /** * Country code, e.g. ES, DE, US. */ @NotNull private Country country; /** * City name. */ @NotBlank private String city;}
Path Type Optional Description
country String false Country code.Must be one of [DE, ES, FR, PT, US].
city String false City name.
@spring_io#springio17
Original: hand-written
2.8. WeatherPOST /weather
Up-to-date weather data for cities around the globe.
[[resources-weather]]= Weather for your city
`POST /weather`
Up-to-date temperature for the given city
@spring_io#springio17
Extension: Javadoc on method/*** Up-to-date weather data for cities around the globe.*/@PostMapping("weather")public WeatherResponse weather( @RequestBody @Valid WeatherRequest weatherRequest) { return new WeatherResponse(20);}
2.8. WeatherPOST /weather
Up-to-date weather data for cities around the globe.
@spring_io#springio17
Original: Path Parameters
.andDo(document("weather", pathParameters( parameterWithName("country").description("Country code"), parameterWithName("city").description("City name"))));
@spring_io#springio17
Extension: Path Parameters/*** Up-to-date weather data for cities around the globe.** @param country Country code.* @param city City name.*/@GetMapping("weather/{country}/{city}")public WeatherResponse weatherPath( @PathVariable @IsoCountryCode String country, @PathVariable String city) { return new WeatherResponse(20);}
@spring_io#springio17
Path Parameters
@spring_io#springio17
Path Parameters
@spring_io#springio17
Original: Query Parameters
.andDo(document("weather", requestParameters( parameterWithName("country").description("Country code"), parameterWithName("city").description("City name"))));
@spring_io#springio17
Extension: Query Parameters/** * Up-to-date weather data for cities around the globe.** @param country Country code.* @param city City name.*/@GetMapping("weatherParam")public WeatherResponse weatherParam( @RequestParam @IsoCountryCode String country, @RequestParam String city) { return new WeatherResponse(20);}
@spring_io#springio17
Query Parameters
@spring_io#springio17
Query Parameters
@spring_io#springio17
Section Snippet[[resources-weather]]=== Weather for your city
`POST /weather`
Up-to-date temperature for the given city
===== Request structure
include::{snippets}/weather/request-fields.adoc[]
===== Response structure
include::{snippets}/weather/response-fields.adoc[]
===== Example request/response
include::{snippets}/weather/curl-request.adoc[]include::{snippets}/weather/http-response.adoc[]
include::{snippets}/weather/section.adoc[]
Documentation path
Spring MVC Controller
Javadoc
Method name
@spring_io#springio17
Authorization snippet
@spring_io#springio17
Content Modifiers
[ 1, 2, 3, 4, 5]
[ 1, 2, 3]
array shortener
@spring_io#springio17
Content Modifiers
%PDF-1.5%����12 0 obj<</Length 3654 /Filter /FlateDecode>>
<binary>
binary replacement
@spring_io#springio17
Benefits
Less to write
Code review Maintainability
Happier developers
DRY
Accurate
@spring_io#springio17
Sounds good!
Issues?
@spring_io#springio17
Issues
@spring_io#springio17
Issues
@spring_io#springio17
Issues
@spring_io#springio17
It’s an extension
Authorization snippet
Javadoc/introspection snippets
Content modifiers
@spring_io#springio17
Spring Auto REST Docsat Scalable Capital
@spring_io#springio17
Spring Auto REST Docsat Scalable Capital
@spring_io#springio17
Thank you
@spring_io#springio17
Q&A
@flbenz