2012-06-04 46 views
63

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.¿Cómo modelar una API RESTful con herencia?

Digamos que tengo Perros y Gatos que heredan de Animales. Necesito operaciones CRUD en perros y gatos; También quiero poder hacer operaciones con animales en general.

Mi primera idea era hacer algo como esto:

GET /animals  # get all animals 
POST /animals  # create a dog or cat 
GET /animals/123 # get animal 123 

Lo que pasa es que la colección/animales es ahora "incompatibles", ya que puede volver a sacar los 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 la creación de una dirección URL para cada tipo de hormigón, así:

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 se pierde la relación entre perros y gatos. 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 la de aumentar la segunda solución mediante la adición de esto:

GET /animals # get common attributes of all animals 

En este caso, los animales volvieron solamente contendría atributos comunes a todos los animales, dejando caer los atributos específicos de cada perro y específicos de cada 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?

Respuesta

31

que sugeriría:

  • El uso de un único URI por recurso
  • La diferenciación entre animales únicamente en el nivel de atributos

Configuración de varios URI al mismo recurso no es una buena idea porque 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 tratar con toda la colección de perros y gatos en el nivel "base" ya se solucionó en virtud del enfoque de URI /animals.

El desafío final de tratar con tipos especializados como perros y gatos se puede resolver fácilmente mediante 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, volvería tanto Rex y manoplas
  • GET /animals?type=dog - obtiene todos los perros, que sólo devuelven Rex
  • GET /animals?type=cat - obtiene todos los gatos, solo mitones

A continuación, al crear o modificar los animales, sería corresponde a la persona que llama para especificar el tipo de animal implicado:

Tipo de medios: application/vnd.vet-services.animal+json

{ 
    "type":"dog", 
    "name":"Fido" 
} 

La carga útil superior podría ser enviado con una POST o PUT solicitud.

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 cambios en su esquema de URI.

+0

Esto parece muy similar a "fundición" a través de una API REST. También me recuerda los problemas/soluciones en el diseño de la memoria de una subclase de C++. Por ejemplo, dónde y cómo representar simultáneamente una base y una subclase con una sola dirección en la memoria. – trcarden

+8

Sugiero: 'GET/animals - gets all dogs and cats' ' GET/animals/dogs - gets all dogs' 'GET/animals/cats - gets all cats' – dipold

+1

Además de especificar el tipo deseado como un Parámetro de solicitud GET: me parece que podría usar accept type para lograr esto también. Es decir: 'GET/animals' Accept' application/vnd.vet-services.animal.dog + json' –

4

me gustaría ir a/animales que regresan una lista de los perros y los peces y lo que nunca má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> 

Debe ser fácil de implementar un ejemplo similar JSON.

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 inherentemente RESTful o unRestful en la devolución de 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 Content-Type).

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 mediante la adición de referencias a los detalles sobre 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> 

Mediante la adición de medios hiper une a reducir el acoplamiento cliente/servidor - en el caso anterior se toma la carga de construir el URL de distancia desde el cliente y dejar que el servidor de decidir cómo para construir URL (que por definición es la única autoridad de).

1

Pero ahora se pierde la relación entre perros y gatos.

De hecho, pero tenga en cuenta que URI simplemente nunca refleja las relaciones entre los objetos.

1

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 utilizando 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 la API abierta (ex Swagger), esquemas de composición se ha mejorado por las palabras clave discriminador (v2.0) y oneof (v3.0) para apoyar verdaderamente polimorfismo.

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition

tu herencia podría modelarse utilizando una combinación de oneof (para la elección de 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 
+0

Es cierto que OAS lo permite. Sin embargo, no hay soporte para la función que se mostrará en Swagger UI ([link] (https://github.com/swagger-api/swagger-ui/issues/2438)), y creo que una característica es limitada. use si no se lo puede mostrar a nadie. – emft

+0

@emft, no es cierto. Al escribir esta respuesta, Swagger UI ya lo admite. –

Cuestiones relacionadas