Servicios Web RESTful (III) - OpenAPI

Publicado por Jaime Martínez el

Arquitectura de SolucionesOpenAPIRESTMicroservicios

Índice

En Mi Local Funciona - RRSS - c. Mar - n8n 1 - 1400x400px.jpg

Introducción

OpenAPI, anteriormente conocido como Swagger, es una especificación para describir servicios web RESTful. Permite a los desarrolladores definir la estructura y el comportamiento de una API de manera estandarizada y comprensible tanto para humanos como para máquinas.

Surgió del proyecto Swagger, iniciado en 2010 por Tony Tam. En 2015, SmartBear Software adquirió Swagger y en 2016, bajo el auspicio de la Fundación Linux, se estableció la Iniciativa OpenAPI para promover su desarrollo.

Se puede encontrar más información en el sitio oficial.

Especificación de OpenAPI

La definición de OpenAPI se puede realizar tanto en formato json como yaml, a partir de este punto seguiremos con este segundo formato.

Se compone de las siguientes secciones:

  • openapi: es la versión utilizada de OpenAPI, no debe confundirse con la versión de la api que se está definiendo.
  • info: da información sobre la api.
  • servers: un listado de servidores para conectar con la api.
  • paths: las rutas y operaciones que ofrece la api.
  • components: contiene los distintos esquemas de los objectos usados en la api.
  • security: define los mecanismos de seguridad necesarios para la autenticación y autorización contra la api.
  • tags: sirven para dar información adicional.
  • externalDocs: documentación externa.

Caso de uso

Aquí se muestra un ejemplo de lo que sería una posible definición de api con la que se verán los principales componentes de la misma y así mismo será la base para una posterior implementación práctica.

openapi: 3.0.4
info:
  title: Sample API
  description: API for use generation with maven plugin
  version: 0.0.1
servers:
  - url: http://localhost:8080/api
    description: Local server
paths:
  /users:
    get:
      summary: List of users
      description: Returns the full list of users without filters
      operationId: read
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Users'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      summary: Create or update a user
      description: Create or update a new user
      operationId: createOrUpdate
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
        required: true
      responses:
        '200':
          description: Created or updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /users/{id}:
    get:
      summary: User the current id
      description: Returns the user filtered by id
      operationId: readOne
      parameters:
        - name: id
          in: path
          description: user id
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
                components:
  schemas:
    Users:
      type: array
      items:
        $ref: '#/components/schemas/User'
    User:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum: [ "400-04", "500-00"]
        message:
          type: string
  • El primer punto es la definición de la versión de OpenAPI que se va a utilizar, en este caso 3.0.4.
openapi: 3.0.4
  • A continuación se muestra la información de la api, que incluye un título, una descripción y la versión actual:
info:
  title: Sample API
  description: API for use generation with maven plugin
  version: 0.0.1
  • En el apatado servers se definen los servidores contra los que se pueden realizar las llamadas, para el caso actual
    localhost. Se compone de:
    • url del servidor.
    • descripción del servidor.
servers:
  - url: http://localhost:8080/api
    description: Local server
  • Bajo paths se definen los distintos endpoints disponibles que se componen de:
    • La ruta relativa (la url completa se forma con el server más el path) al recurso.
    • El método http sorportado, junto a información del mismo, así como posibles parámetros a enviar.
    • En el caso de que exista (métodos POST y PUT) la estructura de la petición que se envía.
    • Las posibles respuestas con la estructura de la información recibida.
paths:
  /users: # ruta relativa que define /users
    get:  # método get permitido sobre /users
      summary: List of users # resumen de la operación que realiza
      description: Returns the full list of users without filters # descripción más detallada sobre la operación que realiza
      operationId: read # se utiliza a la hora de generar el código para indicar el nombre del método encargado de la operación
      responses: # listado de posibles respuestas
        '200': # respuesta cuando todo es correcto con su descripción y contenido
          description: Success
          content:
            application/json: # tipo del contenido, en este caso json
              schema: # estructura de los datos que devuele, se utiliza una referencia para poder reutilizar las definiciones
                $ref: '#/components/schemas/Users'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put: # al estar definido sobre /users indica que aparte del get también se puede utilizar el método put
      summary: Create or update a user
      description: Create or update a new user
      operationId: createOrUpdate
      requestBody: # definición del contenido que se puede enviar en la petición (misma estructura que con las respuestas)
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
        required: true # campo que indica si es obligatorio o no
      responses:
        '200':
          description: Created or updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /users/{id}: # una nueva ruta relativa, el que contenga {} indica que es un parámetro que va en el propio path
    get:
      summary: User the current id
      description: Returns the user filtered by id
      operationId: readOne
      parameters: # parámetros que pueden ir en la petición, en este caso el del path
        - name: id # nombre del parámetro
          in: path # en este caso es a nivel de path, pero también podría ser a nivel de url (query), en una cabecera (header) o en una cookie (cookie)
          description: user id # descripción del campo
          required: true # si es obligatorio
          schema: # tipo, en este caso se indica directamente que es un número
            type: integer
            format: int64
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  • Por último en el apartado componentes se definen las estructuras de datos que se pueden enviar o recibir como se ha visto mediante el uso de $ref.
components:
  schemas:
    Users: # estructura de los usuarios
      type: array # es una seria de elementos
      items:
        $ref: '#/components/schemas/User' # cada elemento es de tipo User
    User:
      type: object # es una estructura que se componene de id y name y ambos son obligatorios
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum: [ "400-04", "500-00"] # en el caso de que una propiedad tenga que tener valores concretos, se define de esta manera
        message:
          type: string

Conclusión

Con OpenAPI se puede definir completa y claramente la estructura de las operaciones de un servicio que siguiendo el enfoque de API-First será la base para la generación de los servidores y clientes, punto que continuará en el último de esta serie de artículos:Servicios Web RESTful (IV) - OpenAPI Generator.

Para profundizar más sobre los contratos OpenAPI Specification.