representaciones - UDFJDC-ModelosProgramacion/Recursos GitHub Wiki

Diseñar un API REST significa:

  1. Decidir cuáles serán los recursos a los que el usuario o cliente del API tendrá acceso.
  2. Por cada recurso decidir cuál será la URL para identificarlo.
  3. Por cada recurso decidir cuál o cuáles serán sus representaciones.
  4. Decidir cuáles serán los servicios (GET, POST, PUT, DELETE) y el significado preciso (semántica) en cada caso.

Vale la pena aclarar que estos pasos son para diseñar un API REST simple que no tiene requisitos de seguridad.

El primer paso para diseñar un API REST es identificar los recursos que la aplicación maneja o va a manejar. Por ejemplo, en el API REST de twitter encontramos recursos como members, users, accounts, tweets, lists, friendships, etc. Es fácil darse cuenta de que esas palabras definen los conceptos que maneja la aplicación. Si tomamos de ejemplo el API REST de Teamwork encontramos recursos como projects, tasks, milestones, people, entre otros.

Para la identificación de los recursos podemos utilizar el listado inicial de requisitos funcionales de la aplicación y el diagrama de clases que representa los conceptos principales que la aplicación debe manejar y las relaciones entre ellos.

El diagrama de clases de la Figura 2 presenta un modelo conceptual parcial de una aplicación para vender libros. Modela que un libro puede tener varios autores, que un autor puede tener varios libros, que un libro tiene una única editorial y que un libro tiene un conjunto propio de revisiones realizadas por revistas o periódicos (sources) asociadas.

Figura 2
Figura 2: Modelo Conceptual

Del modelo conceptual anterior podemos identificar cuatro recursos correspondientes a las clases: Book, Author, Editorial y Review.

Para identificar el conjunto de recursos de un mismo tipo, utilizamos el nombre de la clase en minúscula y en plural. Por ejemplo, para la clase Book vamos a utilizar para el recurso el nombre en plural books. El servicio que retorna todos los libros de la tienda será:

Get /books

La URL completa debe tener la información necesaria para hacer la identificación exacta del servidor y de la aplicación que contiene el recurso, por ejemplo:

uniandes-disc:8080/frontbooks/api/

Para simplificar nuestros ejemplos, sólo indicaremos el nombre del recurso raíz, en este caso books.

Los servicios básicos asociados con el recurso books, utilizando los verbos del protocolo HTTP, se presentan en la Tabla 1:

Servicio sobre el recurso books Descripción
Get /books Retorna la colección de libros de la tienda.
Post /books Crea un nuevo libro y lo agrega a la colección de libros.

Tabla 1: Servicios para el recurso books

En la Figura 3, intentamos representar el concepto del recurso books, y el concepto del recurso el libro identificado con el número 23. Si queremos obtener ese libro, el servicio es:

Get /books/23

Figura 3
Figura 3: Un recurso colección y un recurso individual

En la Tabla 2 definimos los servicios para un recurso individual book de la colección books. En este ejemplo estamos utilizando un identificador único que permite acceder el libro. Podemos diseñar un servicio que tenga el ISBN en vez del identificador o el nombre del libro. También podemos darle un nombre a un recurso especial por ejemplo:

Get /books/elmasvendido

Decidir que el recurso se llama elmasvendido es una decisión del diseño del API, la implementación deberá ocuparse de que ante el pedido Get /books/elmasvendido efectivamente retorne el libro más vendido de la tienda.

Servicio sobre el recurso books Descripción
Get /books/id Retorna un libro con identificador id.
Put / books/id Actualiza el libro con identificador id.
Delete / books/id Borra el libro con identificador id.
Tabla 2: Servicios para un libro identificado con id

Antes de continuar con la identificación de recursos, hablemos sobre qué es lo que se retorna. Se retorna una representación del recurso de acuerdo con nuestra decisión de diseño. Decidimos en qué formato y qué información contiene.

En nuestros APIs REST, siempre vamos a utilizar el formato JavaScript Object Notation JSON para representar los recursos. Esta notación es bastante simple. Suponemos que la información de un recurso se puede representar como una estructura de pares llave-valor o como una lista de estas estructuras. Por ejemplo, si tenemos un recurso estudiante identificado con un id=123. Ante la petición:

Get /students/123

La respuesta en formato JSON será:

{id: 123, name: "Juan", age: 23, city: "Bogotá"}

Si hiciéramos un servicio:

Post /students

Con la petición, en el cuerpo de la petición, debemos enviar en formato JSON la información del nuevo estudiante que queremos crear. Por ejemplo:

{name: "María", age: 21, city: "Cali"}

Del diagrama de clases de la Figura 2 podemos ver que es fácil representar en JSON un objeto de cada una de las clases. Por ejemplo, en formato JSON, un objeto de la clase Book se representaría así:

{
    "id" : 1,
    "name" : "Cien años de soledad",
    "description": "El libro se compone de 20 capítulos no titulados, en los cuales se narra una historia con una estructura cíclica temporal, puesto que los acontecimientos del pueblo y de la familia Buendía, así como los nombres de los personajes, se repiten una y otra vez, fusionando la fantasía con la realidad.",
    "isbn" : "0307474720",
    "image" : "http://goo.gl/IWNdCX",
    "publishingDate" : "01071967"
}

La decisión de diseño puede ser que cada recurso se representa siguiendo el modelo de las clases. Si bien es una decisión simple de diseño, esta deja por fuera las asociaciones. Debemos preguntarnos: ¿Cómo representamos las asociaciones entre las clases? Del ejemplo JSON anterior que representa el libro que se llama "Cien años de soledad", esta representación no tiene información acerca de cuál es la editorial del libro.

A continuación vamos a explicar una solución para este problema de diseño.

Para las aplicaciones de nuestros ejemplos, hemos tomado la decisión de diseño de tener para cada recurso dos representaciones JSON diferentes:

  • Básica: contiene los atributos propios de la clase y los de la clase destino de las asociaciones de cardinalidad 1.
  • Detallada; contiene los atributos de la representación básica y las representaciones básicas de las relaciones (distintas a las de carnidalidad 1).

Para facilitar la expresión de esta solución, vamos a transformar el modelo conceptual en un diagrama de Data Transfer Objects (DTO) que son las clases cuyas instancias serán transformadas en objetos JSON.

La Figura 4 muestra un modelo conceptual y una transformación del modelo conceptual hacia un diagrama de DTOs siguiendo la decisión de diseño anterior. El recurso Ciudad solo tiene una representación básica llamada CiudadDTO que contiene los atributos del recurso. No tiene una representación detallada porque, en este ejemplo, de la clase Ciudad no sale ninguna asociación hacia otras clases. Lo mismo sucede con el recurso Viaje que solo tiene la representación básica ViajeDTO. El recurso Viajero si tiene las dos representaciones:

  • La básica, ViajeroDTO, que contiene los atributos de la clase Viajero y la relación a la representación básica de Ciudad (CiudadDTO) porque esta asociación va de Viajero a Ciudad y es de cardinalidad 1.
  • La detallada, ViajeroDetailDTO que contiene la colección de representaciones básicas de la clase ViajeDTO.
Figura 4
Figura 4: Representaciones de los recursos

Veamos más ejemplos utilizando el modelo conceptual del ejemplo Book.

Representaciones del recurso - book

Tomando como partida el diagrama de clases de UML presentado más arriba, tenemos la siguiente transformación a un diagrama de DTOs conforme con nuestra decisión de diseño.

Figura 5
Figura 5: Representaciones del recurso book

Los recursos editorials y reviews solo tienen una representación básica porque no tienen relaciones con las demás clases.

Los recursos books y authors tienen tanto representaciones básicas (BookDTO y AuthorDTO) como detalladas (BookDetailDTO y AuthorDetailDTO).

Representación básica recurso - book

La representación básica para cada recurso contiene los atributos de la clase y la representación básica de las clases con las que se tienen relaciones uno-a-uno y muchos-a-uno.

Para el recurso book, la representación básica incluye los atributos de la clase Book y la representación básica del objeto Editorial con el que se relaciona.

Estructura JSON
{
    id: '' /*Tipo Long*/,
    name: '' /*Tipo String*/,
    isbn: '' /*Tipo String*/,
    description: '' /*Tipo String*/,
    image: '' /*Tipo String*/,
    publishingdate: '' /*Tipo Date*/
    editorial: {
      id: '' /*Tipo Long*/,
      name: '' /*Tipo String*/
    }
}

Este es un ejemplo de una representación básica de Book.

{
    "id" : 1,
    "name" : "Cien años de soledad",
    "description": "El libro se compone de 20 capítulos no titulados, en los cuales se narra una historia con una estructura cíclica temporal, puesto que los acontecimientos del pueblo y de la familia Buendía, así como los nombres de los personajes, se repiten una y otra vez, fusionando la fantasía con la realidad.",
    "isbn" : "0307474720",
    "image" : "http://goo.gl/IWNdCX",
    "publishDate" : "01071967",
    "editorial": {"id" : 1, "name" : "Plaza y Janes"}
}

Es importante notar en el ejemplo anterior que la representación básica de book contiene la representación básica del recurso editorial.

Representación Detallada recurso - book

En los proyectos de ejemplo del curso, la representación detallada incluye los atributos de la clase y las colecciones de las representaciones detalladas de todas las otras clases con las que se relaciona (de cardinalidad múltiple).

Esta es la representación detallada para la clase Book. Note que incluye las colecciones de las representaciones detalladas de Review y de Author.

{
    id: '' /*Tipo Long*/,
    name: '' /*Tipo String*/,
    source: '' /*Tipo String*/,
    description: '' /*Tipo String*/ ,
    editorial: {
        id: '' /*Tipo Long*/,
        name: '' /*Tipo String*/
    }
    reviews: [
        {id: '' /*Tipo Long*/,
        name: '' /*Tipo String*/,
        source: '' /*Tipo String*/,
        description: '' /*Tipo String*/}
    ],
    authors:[
        {id: '' /*Tipo Long*/,
        name: '' /*Tipo String*/,
        birthdate: '' /*Tipo String con formato fecha*/}
    ]
}

Este es un ejemplo de una representación detallada de Book.

{
"id" : 1,
"name" : "Cien años de soledad",
"description": "El libro se compone de 20 capítulos no titulados, en los cuales se narra una historia con una estructura cíclica temporal, puesto que los acontecimientos del pueblo y de la familia Buendía, así como los nombres de los personajes, se repiten una y otra vez, fusionando la fantasía con la realidad.",
"isbn" : "0307474720",
"image" : "http://goo.gl/IWNdCX",
"publishingDate" : "01071967",
"editorial": {"id" : 1, "name" : "Plaza y Janes"},
"authors":[
     {
     "id": 200,
    "name": "Gabriel José de la Concordia García Márquez"
            "birthDate": "1927-03-03T00:00:00-05:00",
    "description": "fue un escritor, guionista, editor y periodista colombiano. En 1982 recibió el Premio Nobel de Literatura.",
    "image": "https://commons.wikimedia.org/wiki/File:Gabriel_Garcia_Marquez.jpg",
    }
 ],
 "reviews":[
    {
     "id": 123,
     "name": "Primera edición",
     "source":"El Tiempo",
     "description":"Magnifico inigualable"
    },
    {
     "id": 456,
     "name": "Realismo",
     "source":"Arcadia",
     "description":"Realismo mágico al extremo. No puede dejar de leerlo."
    }
  ]
}

Estamos llamando a un recurso Recurso raíz cuando queremos ofrecer los servicios de crear y acceder al recurso de manera directa. En nuestro ejemplo es el caso del recurso books, authors y editorials.

Asociación Book-Review

Los reviews no son recursos raíz porque son dependientes del libro al que pertenecen. Esto lo decidimos cuando definimos un rombo negro (composite) en la asociación entre Book y Review en el modelo de clases conceptual.

Esto implica que para crear un review> tengo primero que identificar el libro al que le va a pertenecer. En términos del diseño del API significa que la url del recurso reviews es:

/books/23/reviews

Esta URL identifica el recurso reviews del libro cuyo identificador es 23.

_Decimos entonces, que reviews es un subrecurso de books.

Los servicios que vamos a ofrecer para este subrecurso son:

Servicio sobre el subrecurso books/id/reviews Descripción
Get /books/id/reviews Retorna los reviews del libro con identificador id.
Post /books/id/reviews Crea un nuevo review para el libro con identificador id.
Put /books/id/reviews/id2 Actualiza el review identificado con id2 del libro con identificador id.
Delete /books/id/reviews/id2 Borra del libro con identificador id el review con identificador id2.

Asociación Book-Author

En el diagrama de clases concetual Book y Author son identificados como recursos raíz. Estos recursos tienen sus propios servicios para crear un book y para crear un author:

Post /books

Post /authors

Tenemos que tener entonces un servicio que permita asociar un author a un book. Esta asociación también la definimos como un subrecurso teniendo como url:

/books/23/authors

Esta url identifica el recurso authors del libro cuyo identificador es 23.

Los servicios que vamos a ofrecer para este subrecurso son:

Servicio sobre el subrecurso books/id/reviews Descripción
Get / books/id/authors Retorna los authors del libro con identificador id.
Post / books/id/authors/id2 Crea una nueva asociación entre el libro con identificador id y el author con identificador id2.
Delete / books/id/authors/id2 Elimina la asociación entre del libro con identificador id el author con identificador id2.

Note que en el POST del caso de reviews se crea el review porque este no existe, mientras que, el caso de authors, crea la asociación entre el libro que ya existe y el autor que también existe.

⚠️ **GitHub.com Fallback** ⚠️