estandares - qué es una api rest y para qué sirve
¿Cómo modelar una API RESTful con herencia? (4)
Tengo una jerarquía de objetos que necesito exponer a través de una API RESTful y no estoy seguro de cómo deberían estructurarse mis URL y qué deberían devolver. No pude encontrar las mejores prácticas.
Digamos que tengo perros y gatos que heredan de los animales. Necesito operaciones CRUD en perros y gatos; También quiero poder hacer operaciones con animales en general.
Mi primera idea fue hacer algo como esto:
GET /animals # get all animals
POST /animals # create a dog or cat
GET /animals/123 # get animal 123
La cosa es que la colección / animals ahora es "inconsistente", ya que puede regresar y tomar objetos que no tienen exactamente la misma estructura (perros y gatos). ¿Se considera "RESTful" tener una colección que devuelve objetos que tienen diferentes atributos?
Otra solución sería crear una URL para cada tipo concreto, como esta:
GET /dogs # get all dogs
POST /dogs # create a dog
GET /dogs/123 # get dog 123
GET /cats # get all cats
POST /cats # create a cat
GET /cats/123 # get cat 123
Pero ahora la relación entre perros y gatos se pierde. Si uno desea recuperar todos los animales, se deben consultar los recursos de ambos perros y gatos. El número de URL también aumentará con cada nuevo subtipo de animal.
Otra sugerencia fue aumentar la segunda solución al agregar esto:
GET /animals # get common attributes of all animals
En este caso, los animales devueltos solo contendrían atributos comunes a todos los animales, eliminando los atributos específicos del perro y del gato. Esto permite recuperar todos los animales, aunque con menos detalles. Cada objeto devuelto podría contener un enlace a la versión detallada y concreta.
¿Algún comentario o sugerencia?
Pero ahora la relación entre perros y gatos se pierde.
De hecho, pero tenga en cuenta que el URI simplemente nunca refleja las relaciones entre los objetos.
Esta pregunta se puede responder mejor con el apoyo de una mejora reciente introducida en la última versión de OpenAPI.
Ha sido posible combinar esquemas usando palabras clave como oneOf, allOf, anyOf y obtener un mensaje de carga validado desde el esquema JSON v1.0.
https://spacetelescope.github.io/understanding-json-schema/reference/combining.html
Sin embargo, en el OpenAPI (anterior Swagger), la composición de los esquemas se ha mejorado con el discriminador de palabras clave (v2.0 +) y oneOf (v3.0 +) para apoyar realmente el polimorfismo.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition
Su herencia se puede modelar usando una combinación de oneOf (para elegir uno de los subtipos) y allOf (para combinar el tipo y uno de sus subtipos). A continuación se muestra una definición de muestra para el método POST.
paths:
/animals:
post:
requestBody:
content:
application/json:
schema:
oneOf:
- $ref: ''#/components/schemas/Dog
- $ref: ''#/components/schemas/Cat
- $ref: ''#/components/schemas/Fish
discriminator:
propertyName: animal_type
responses:
''201'':
description: Created
components:
schemas:
Animal:
type: object
required:
- animal_type
- name
properties:
animal_type:
type: string
name:
type: string
discriminator:
property_name: animal_type
Dog:
allOf:
- $ref: "#/components/schemas/Animal"
- type: object
- properties:
playsFetch
type: string
Cat:
allOf:
- $ref: "#/components/schemas/Animal"
- type: object
- properties:
likesToPurr
type: string
Fish:
allOf:
- $ref: "#/components/schemas/Animal"
- type: object
- properties:
water-type
type: string
Me gustaría ir / animals devolviendo una lista de perros y peces y todo lo demás:
<animals>
<animal type="dog">
<name>Fido</name>
<fur-color>White</fur-color>
</animal>
<animal type="fish">
<name>Wanda</name>
<water-type>Salt</water-type>
</animal>
</animals>
Debería ser fácil implementar un ejemplo JSON similar.
Los clientes siempre pueden confiar en que el elemento "nombre" esté allí (un atributo común). Pero dependiendo del atributo "tipo" habrá otros elementos como parte de la representación del animal.
No hay nada intrínsecamente RESTful o UnRestful en devolver dicha lista: REST no prescribe ningún formato específico para representar datos. Todo lo que dice es que los datos deben tener alguna representación y el formato para esa representación se identifica por el tipo de medio (que en HTTP es el encabezado de tipo de contenido).
Piense en sus casos de uso: ¿necesita mostrar una lista de animales mezclados? Bueno, devuelve una lista de datos de animales mezclados. ¿Necesitas una lista de perros solamente? Bueno, haz esa lista.
Si lo hace / animals? Type = dog o / dogs es irrelevante con respecto a REST, que no prescribe ningún formato de URL, que se deja como un detalle de implementación fuera del alcance de REST. REST solo indica que los recursos deben tener identificadores, sin importar qué formato.
Debe agregar algunos enlaces de hipermedia para acercarse a una API RESTful. Por ejemplo, al agregar referencias a los detalles de los animales:
<animals>
<animal type="dog" href="/animals/123">
<name>Fido</name>
<fur-color>White</fur-color>
</animal>
<animal type="fish" href="/animals/321">
<name>Wanda</name>
<water-type>Salt</water-type>
</animal>
</animals>
Al agregar hipervínculo multimedia, reduce el acoplamiento cliente / servidor; en el caso anterior, quita la carga de la construcción de URL del cliente y deja que el servidor decida cómo construir URL (que por definición es la única autoridad de).
Yo sugeriría:
- Usar solo un URI por recurso
- Diferenciar entre animales únicamente en el nivel de atributo
Nunca es una buena idea configurar varios URI para el mismo recurso, ya que puede causar confusión y efectos secundarios inesperados. Dado que, su único URI debe basarse en un esquema genérico como /animals
.
El próximo reto de lidiar con toda la colección de perros y gatos a nivel "base" ya se solucionó en virtud del enfoque URI /animals
.
El desafío final de tratar con tipos especializados como perros y gatos se puede resolver fácilmente usando una combinación de parámetros de consulta y atributos de identificación dentro de su tipo de medio. Por ejemplo:
GET /animals
( Accept : application/vnd.vet-services.animals+json
)
{
"animals":[
{
"link":"/animals/3424",
"type":"dog",
"name":"Rex"
},
{
"link":"/animals/7829",
"type":"cat",
"name":"Mittens"
}
]
}
-
GET /animals
- obtiene todos los perros y gatos, devolvería Rex y Mittens -
GET /animals?type=dog
- obtiene todos los perros, solo devolverá Rex -
GET /animals?type=cat
- obtiene todos los gatos, solo Mittens
Luego, al crear o modificar animales, correspondería a la persona que llama especificar el tipo de animal involucrado:
Tipo de medio: application/vnd.vet-services.animal+json
{
"type":"dog",
"name":"Fido"
}
La carga útil anterior podría enviarse con una POST
o PUT
.
El esquema anterior le ofrece las características básicas similares a la herencia OO a través de REST y la posibilidad de agregar más especializaciones (es decir, más tipos de animales) sin cirugía mayor o cualquier cambio en su esquema de URI.