Components
Ya vimos un poquito sobre cómo funcionan los components pero vamos a profundizar mejor en eso.
En OpenAPI, la sección components sirve para tornar objetos estandarizados y reutilizables, como definiciones de modelos de datos, parámetros, respuestas, seguridad y otros.
El objetivo principal de esta estructura es:
- Evitar la repetición de definiciones en varias partes del documento OpenAPI
- Facilitar el mantenimiento
- Mejorar la organización y facilitar la lectura
- Mejora la colaboración y experiencia de desarrollo
- Mejorar la calidad de la especificación
- Reducir el tamaño de la especificación
Un overview inicial de components.
components:
schemas: # Modelos de datos reutilizables
## ...
parameters: # Parámetros reutilizables
## ...
requestBodies: # requestBodies reutilizables
## ...
responses: # Respuestas reutilizables
## ...
headers: # Headers reutilizables
## ...
securitySchemes: # Esquemas de seguridad reutilizables
## ...
links: # Links entre operaciones
## ...
callbacks: # Definiciones de callbacks (notificaciones asíncronas)
## ...
pathItems: # Definiciones de pathItems
## ...
examples: # Ejemplos de objetos para los datos de entrada y salida
## ...
Si tienes 5 endpoints que usan el mismo objeto podemos apenas usar una referencia al objeto en vez de definirlo en cada uno de los endpoints. Esto evitará que un cambio sea necesario ser hecho en todos los lugares y evitará errores además de la reducción de código.
Usamos el $ref para apuntar donde están las definiciones de lo que necesitamos. Esas definiciones pueden estar en la propia especificación, en otros archivos y hasta podemos usar links referenciando una url.
Cuando la especificación comienza a quedar muy grande, separar algunas cosas pueden ayudar en la organización.
Aquí estamos referenciando un schema pet dentro de components dentro de la propia especificación. #/ quiere decir la raíz del documento y después components.schemas.Pet.
$ref: '#/components/schemas/Pet'
Un archivo con lo que necesitamos en el mismo directorio.
$ref: 'Pet.json'
Usando una url para apuntar el archivo
$ref: 'https://example.com/api/schemas/Pet.json'
Si #/ representa la raíz del documento, entonces abajo estamos buscando en Owner a partir de la raíz. Es la misma lógica en el mismo archivo, pero ahora apuntando para otro antes de ese.
$ref: 'https://example.com/api/schemas/Pet.json#/Owner'
# $ref: 'Pet.json#/Owner' <<<<< Sería la misma idea

Vamos a partir de este ejemplo y comentar dentro del 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 a traer del 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:
#####
# Aquí definir una dirección
Address: # La convención dice para crear esos schemas iniciando con letra mayú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"
#####
#####
# Aquí definimos un customer que además de sus definiciones posee una dirección
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:
## Observe que la referencia es siempre a partir de la raíz. No es posible referenciar con caminos relativos
$ref: '#/components/schemas/Address' #<<<< Traerá todo lo que tiene dentro de Address. O sea, recibirá el objeto entero.
description: customer object
#####
CustomerList:
maxItems: 100
minItems: 1
type: array
description: List of Customers
items:
$ref: '#/components/schemas/Customer' # ¿Ya pensaste si necesitásemos redefinir el item customer entero para definir el item?
####
v1beers_brewery: # Fuera de la convención este nombre (Funciona pero sale fuera del estándar)
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 el objeto cervecería nuevamente.
description: Beer Object
Observe que definimos objetos una única vez. Un objeto no posee otro objeto dentro, cuando eso ocurre creamos una referencia para aislar y reaprovecharlo.
Podemos ver la estructura de los schemas usando el editor.


Herencia
Podemos heredar las propiedades de otro objeto y extender aún. Es muy útil crear un objeto a partir de varios objetos. Este es un recurso muy poderoso ya explorado en lenguajes orientados a objetos que podemos reaprovechar el concepto en OpenAPI.
components:
schemas:
##...
###
BeerList:
type: array
items:
$ref: '#/components/schemas/Beer'
### Aquí estamos creando un nuevo objeto que hará la herencia de PageResponse, pero vamos a incluir una propiedad llamada content que será una lista de cerveza.
###
BeerPagedList:
type: object
properties: # Lo que vamos a extender
content:
$ref: '#/components/schemas/BeerList'
# otro:
# $ref: '#/components/schemas/Otro'
allOf: # la lista de lo que vamos a heredar de otros 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
