OpenAPI Components
Já vimos um pouquinho sobre como funciona os components mas vamos nos aprofundar melhor nisso.
No OpenAPI, a seção components serve para tornar objetos padronizados e reutilizáveis, como definições de modelos de dados, parâmetros, respostas, segurança e outros.
O objetivo principal dessa estrutura é:
- Evitar a repetição de definições em várias partes do documento OpenAPI
- Facilitar a manutenção
- Melhorar a organização e facilitar a leitura
- Melhora a colaboração e experiência de desenvolvimento
- Melhorar a qualidade da especificação
- Reduzir o tamanho da especificação
Um overview inicial de componenets.
components:
schemas: # Modelos de dados reutilizáveis
## ...
parameters: # Parâmetros reutilizáveis
## ...
requestBodies: # requestBodies reutilizáveis
## ...
responses: # Respostas reutilizáveis
## ...
headers: # Headers reutilizáveis
## ...
securitySchemes: # Esquemas de segurança reutilizáveis
## ...
links: # Links entre operações
## ...
callbacks: # Definições de callbacks (notificações assíncronas)
## ...
pathItems: # Definições de pathItems
## ...
examples: # Exemplos de objetos para os dados de entrada e saída
## ...
Se tiver 5 endpoints que usam o mesmo objeto podemos apenas usar uma referência ao objeto ao invés de definí-lo em cada um dos endpoins. Isso evitará que uma mudança seja necessária ser feitas em todos os lugares e eviterá erros além da redução de código.
Usamos o $ref
para apontar onde estão as definições do que precisamos. Essas definições podem estar na própria especificação, em outros arquivos e até podemos usar links referenciando uma url.
Quando a especificação começa a ficar muito grande, separar algumas coisas podem ajudar na organização.
Aqui estamos referenciando um schema pet dentro de components dentro da própria especificação. #/
quer dizer a raiz do documento e depois components.schemas.Pet.
$ref: '#/components/schemas/Pet'
Um arquivo com o que precisamos no mesmo diretório.
$ref: 'Pet.json'
Usando uma url para apontar o arquivo
$ref: 'https://example.com/api/schemas/Pet.json'
se #/
representa a raiz do do documento, então abaixo estamos procurando em Owner a partir da raiz. É a mesma lógico no mesmo arquivo, mas agora apontando para outro antes desse.
$ref: 'https://example.com/api/schemas/Pet.json#/Owner'
# $ref: 'Pet.json#/Owner' <<<<< Seria a mesma idéia
Vamos partir deste exemplo e comentar dentro do código.
openapi: 3.0.2
info:
title: OpenAPI Course
description: Specification for OpenAPI Course
termsOfService: http://example.com/terms/
contact:
name: John Thompson
url: https://springframework.guru
email: [email protected]
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
version: "1.0"
servers:
- url: https://dev.example.com
description: Development Server
- url: https://qa.example.com
description: QA Server
- url: https://prod.example.com
description: Production Server
paths:
/v1/customers:
get:
responses:
200:
description: List of Customers
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerList' # Vamos trazer do components
/v1/beers:
get:
responses:
200:
description: List of Beers
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/inline_response_200'
404:
description: No Beers Found
components:
schemas:
#####
# Aqui definir um endereço
Address: # A convenção diz para criar esses schemas iniciando com letra maiúscula
type: object
properties:
line1:
type: string
example: 123 main
city:
type: string
example: St Pete
stateCode:
maxLength: 2
minLength: 2
type: string
description: 2 Letter State Code
enum:
- AL
- AK
- AZ
- AR
- CA
zipCode:
type: string
example: "33701"
#####
#####
# Aqui definimos um customer que além das suas definições possui um endereço
Customer:
type: object
properties:
id:
type: string
format: uuid
firstName:
maxLength: 100
minLength: 2
type: string
example: John
lastName:
maxLength: 100
minLength: 2
type: string
example: Thompson
address:
## Obseve que a referência é sempre a partir da raiz. Não é possível referenciar com caminhos relativos
$ref: '#/components/schemas/Address' #<<<< Irá trazer tudo que tem dentro de Address. Ou seja, irá receber o objeto inteiro.
description: customer object
#####
CustomerList:
maxItems: 100
minItems: 1
type: array
description: List of Customers
items:
$ref: '#/components/schemas/Customer' # Já pensou se precisassemos redefinir o item customer inteiro para definir o item?
####
v1beers_brewery: # Fora da convenção este nome (Funciona mas sai fora do padrão)
type: object
properties:
name:
type: string
location:
type: string
####
inline_response_200:
type: object
properties:
beerName:
type: string
style:
type: string
enum:
- ALE
- PALE_ALE
- IPA
- WHEAT
- LAGER
price:
type: number
format: float
quantityOnHand:
type: integer
format: int32
brewery:
$ref: '#/components/schemas/v1beers_brewery' # Usamos o objeto cervejaria novamente.
description: Beer Object
Observe que definimos objetos uma única vez. Um objeto não possui outro objeto dentro, quando isso ocorre criamos uma referência para isolar e reaproveitá-lo.
Podemos ver a estrutura dos schemas usando o editor.
Herança
Podemos herdar as propriedades de um outro objeto e extender ainda. É muito útil criar um objeto a partir de vários objetos. Esse é um recurso muito poderoso já explorado em linguagens orientada a objetos que podemos reaproveitar o conceito no OpenAPI.
components:
schemas:
##...
###
BeerList:
type: array
items:
$ref: '#/components/schemas/Beer'
### Aqui estamos criando um novo objeto que fará a herança de PageResponse, mas vamos incluir uma propriedade chamada content que será uma lista de cerveja.
###
BeerPagedList:
type: object
properties: # O que vamo extender
content:
$ref: '#/components/schemas/BeerList'
# outro:
# $ref: '#/components/schemas/Outro'
allOf: # a lista do que vamos herdar de outros objetos
- $ref: '#/components/schemas/PagedResponse'
# - $ref: '#/components/schemas/Other'
PagedResponse:
type: object
properties:
pageable:
$ref: '#/components/schemas/PagedResponse_pageable'
totalPages:
type: integer
format: int32
last:
type: boolean
totalElements:
type: integer
format: int32
size:
type: integer
format: int32
number:
type: integer
format: int32
numberOfElements:
type: integer
format: int32
sort:
$ref: '#/components/schemas/PagedResponse_pageable_sort'
first:
type: boolean
PagedResponse_pageable_sort:
type: object
properties:
sorted:
type: boolean
unsorted:
type: boolean
PagedResponse_pageable:
type: object
properties:
sort:
$ref: '#/components/schemas/PagedResponse_pageable_sort'
offset:
type: integer
format: int32
pageNumber:
type: integer
format: int32
pageSize:
type: integer
format: int32
paged:
type: boolean
unpaged:
type: boolean