Post
KO

OpenAPI/swagger

RESTful 서비스를 문서화할 때 가장 일반적으로 사용하는 사양이며, 주요 API gateway의 상당수가 swagger를 기본 지원한다.

​

Spring Fox

Spring fox를 사용하면 API를 구현하는 소스 코드와 연동해 API를 문서화할 수 있다. 이런 기능은 매우 중요한데, 자바 코드와 API 문서의 수명 주기가 다르면 시간이 지나면서 쉽게 어긋나기 때문이다.

​

생각보다 빠르게 어긋난다고 한다.

​

RESTful API의 문서화 관점에서 보면 API를 구현하는 Java Class가 아닌 Java Interface에 API 문서를 추가하는 게 낫다.

​

2015년 스마트베어 소프트웨어(SmartBear Software)가 리눅스 재단(Linux Foundation) 산하의 Open API 이니셔티브(Initiative)에 Swagger 사양을 기부하고 OpenAPI 사양을 만들었다.

​

Spring fox는 개발자가 설정한 구성 정보나 Spring Webflux, Swagger Annotation에서 얻은 구성 정보를 바탕으로 API 문서를 생성한다.

​

OpenAPI는 Spring fox v3에서 지원할 예정이다.

​

build.gradle

implementation('io.springfox:springfox-boot-starter:3.0.0')

​

configuration

@SpringBootApplication @ComponentScan("se.magnus") public class ProductCompositeServiceApplication { @Value("${api.common.version}") String apiVersion; @Value("${api.common.title}") String apiTitle; @Value("${api.common.description}") String apiDescription; @Value("${api.common.termsOfServiceUrl}") String apiTermsOfServiceUrl; @Value("${api.common.license}") String apiLicense; @Value("${api.common.licenseUrl}") String apiLicenseUrl; @Value("${api.common.contact.name}") String apiContactName; @Value("${api.common.contact.url}") String apiContactUrl; @Value("${api.common.contact.email}") String apiContactEmail; .... @Bean public Docket apiDocumentation() { return new Docket(SWAGGER_2) .select() .apis(basePackage("se.magnus.microservices.composite.product")) .paths(PathSelectors.any()) .build() .globalResponseMessage(GET, emptyList()) .apiInfo(new ApiInfo( apiTitle, apiDescription, apiVersion, apiTermsOfServiceUrl, new Contact(apiContactName, apiContactUrl, apiContactEmail), apiLicense, apiLicenseUrl, emptyList() )); } }

- Swagger v2 문서를 생성하고자 Docket Bean을 초기화 한다.

- apis(), paths() method로 spring fox가 api 정보를 찾을 위치를 지정한다.

- globalREsponseMessage() method를 사용해 spring fox가 기본 http 응답 코드(예: 401, 403)을 추가하지 않게 한다.(예제에서는 응답코드가 불필요해서 빠져있다.)

- api* 변수로 Docket bean을 구성한다. 구성에 들어가는 API 정보를 spring의 @Value annotation을 사용해 속성 파일에서 초기화된다. @Value annotation은 다음과 같다.

@Value("${api.common.version}") String apiVersion; @Value("${api.common.title}") String apiTitle; @Value("${api.common.description}") String apiDescription; @Value("${api.common.termsOfServiceUrl}") String apiTermsOfServiceUrl; @Value("${api.common.license}") String apiLicense; @Value("${api.common.licenseUrl}") String apiLicenseUrl; @Value("${api.common.contact.name}") String apiContactName; @Value("${api.common.contact.url}") String apiContactUrl; @Value("${api.common.contact.email}") String apiContactEmail;

​

​

application.yml에 작성된 내용

api: common: version: 1.0.0 title: Sample API description: Description of the API... termsOfServiceUrl: MINE TERMS OF SERVICE URL license: License licenseUrl: MY LICENSE URL contact: name: Contact url: My email: me@mail.com product-composite: get-composite-product: description: Returns a composite view of the specified product id notes: | # Normal response If the requested product id is found the method will return information regarding: 1. Base product information 1. Reviews 1. Recommendations 1. Service Addresses\n(technical information regarding the addresses of the microservices that created the response) # Expected partial and error responses In the following cases, only a partial response be created (used to simplify testing of error conditions) ## Product id 113 200 - Ok, but no recommendations will be returned ## Product id 213 200 - Ok, but no reviews will be returned ## Non numerical product id 400 - A **Bad Request** error will be returned ## Product id 13 404 - A **Not Found** error will be returned ## Negative product ids 422 - An **Unprocessable Entity** error will be returned

API 정보 추가

API의 정보를 제공하는 @ApiOperation annotation을 java interface에 추가한다. 각 RESTful 오퍼레이션의 해당 java method에 @ApiResponse annotation 및 @ApiOperation annotation을 추가해 오퍼레이션과 오류 응답을 설명한다.

​

spring fox는 spring의 @GetMapping annotation을 검사해 오퍼레이션의 입력 매개변수와 응답유형을 파악한다.

​

리소스 수준의 API 정보는 다음과 같이 작성한다.

@Api(description = "REST API for composite product information.") public interface ProductCompositeService {

단일 API 오퍼레이션을 문서화 하는 방법은 다음과 같이 작성한다.

@ApiOperation( value = "${api.product-composite.get-composite-product.description}", notes = "${api.product-composite.get-composite-product.notes}") @ApiResponses(value = { @ApiResponse(code = 400, message = "Bad Request, invalid format of the request. See response message for more information."), @ApiResponse(code = 404, message = "Not found, the specified id does not exist."), @ApiResponse(code = 422, message = "Unprocessable entity, input parameters caused the processing to fails. See response message for more information.") }) @GetMapping( value = "/product-composite/{productId}", produces = "application/json") ProductAggregate getProduct(@PathVariable int productId);

@ApiOperation swagger annotation의 값을 지정할 때는 @Value annotation을 사용하지 않고 속성 자리 표시자를 직접 사용한다. (ex: ${bar.foo})

@ApiResponses anntation 안에 있는 오류 코드에 대해선 속성 자리 표시자를 사용할 수 없다. 따라서 각 오류 코드에 대한 설명을 java source code에 직접 입력한다.

​

![](/assets/images/posts/222975734759/de9affc6cadf.png?type=w580)

- Spring fox Docket Bean에 지정한 일반 정보, 실제 스웨거 문서의 링크 (http://localhost:8080/v2/api-docs)

- API 리소스 목록: 지금은 product-composite-service api만 있다.

- page 하단에는 API엣 사용하는 모델을 검사하는 항목이 있다.

​

![](/assets/images/posts/222975734759/0ceba70fd719.png?type=w580)

- 오퍼레이션에 대한 한 줄 설명

- 지원하는 입력 매개 변수 등 오퍼레이션을 자세히 설명하는 항목. @ApiOperation annotation에 markdown 문법으로 입력한 내용이 랜더링 된다.

​

Spring fox는 spring webflux와 swagger annotation을 검사해서 swagger 기반 API 문서를 런타임에 즉시 생성하는 오픈 소스 프로젝트다. API를 설명하는 텍스트는 java source code의 annotation에 넣을 수 있으며, 쉽게 텍스트를 편집할 수 있도록 속성파일에 넣을 수도 있다.

​

/v2/api-docs/ 호출시 응답

// 20230105154050 // <http://localhost:8080/v2/api-docs#/definitions/RecommendationSummary> { "swagger": "2.0", "info": { "description": "Description of the API...", "version": "1.0.0", "title": "Sample API", "termsOfService": "MINE TERMS OF SERVICE URL", "contact": { "name": "Contact", "url": "My", "email": "me@mail.com" }, "license": { "name": "License", "url": "MY LICENSE URL" } }, "host": "localhost:8080", "basePath": "/", "tags": [ { "name": "product-composite-service-impl", "description": "REST API for composite product information." } ], "paths": { "/product-composite/{productId}": { "get": { "tags": [ "product-composite-service-impl" ], "summary": "Returns a composite view of the specified product id", "description": "# Normal response\nIf the requested product id is found the method will return information regarding:\n1. Base product information\n1. Reviews\n1. Recommendations\n1. Service Addresses\\n(technical information regarding the addresses of the microservices that created the response)\n\n# Expected partial and error responses\nIn the following cases, only a partial response be created (used to simplify testing of error conditions)\n\n## Product id 113\n200 - Ok, but no recommendations will be returned\n\n## Product id 213\n200 - Ok, but no reviews will be returned\n\n## Non numerical product id\n400 - A **Bad Request** error will be returned\n\n## Product id 13\n404 - A **Not Found** error will be returned\n\n## Negative product ids\n422 - An **Unprocessable Entity** error will be returned\n", "operationId": "getProductUsingGET", "produces": [ "application/json" ], "parameters": [ { "name": "productId", "in": "path", "description": "productId", "required": true, "type": "integer", "format": "int32" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/ProductAggregate" } }, "400": { "description": "Bad Request, invalid format of the request. See response message for more information." }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not found, the specified id does not exist." }, "422": { "description": "Unprocessable entity, input parameters caused the processing to fails. See response message for more information." } } } } }, "definitions": { "ProductAggregate": { "type": "object", "properties": { "name": { "type": "string" }, "productId": { "type": "integer", "format": "int32" }, "recommendations": { "type": "array", "items": { "$ref": "#/definitions/RecommendationSummary" } }, "reviews": { "type": "array", "items": { "$ref": "#/definitions/ReviewSummary" } }, "serviceAddresses": { "$ref": "#/definitions/ServiceAddresses" }, "weight": { "type": "integer", "format": "int32" } }, "title": "ProductAggregate" }, "RecommendationSummary": { "type": "object", "properties": { "author": { "type": "string" }, "rate": { "type": "integer", "format": "int32" }, "recommendationId": { "type": "integer", "format": "int32" } }, "title": "RecommendationSummary" }, "ReviewSummary": { "type": "object", "properties": { "author": { "type": "string" }, "reviewId": { "type": "integer", "format": "int32" }, "subject": { "type": "string" } }, "title": "ReviewSummary" }, "ServiceAddresses": { "type": "object", "properties": { "cmp": { "type": "string" }, "pro": { "type": "string" }, "rec": { "type": "string" }, "rev": { "type": "string" } }, "title": "ServiceAddresses" } } }

​

This article is licensed under CC BY 4.0 by the author.