Diseñando una API REST – I

Bienvenidos de nuevo! Tras meditarlo largo y tendido durante este último mes he decidido que la siguiente publicación versará sobre la creación de una API REST, usando .Net para los ejemplos de código. Para dejarlo más claro daremos un repaso a ciertos conceptos y buenas prácticas para un correcto diseño de API REST. Puesto que crear y exponer una aplicación es bastante sencillo y no me refiero sólo por el uso del Framework .Net, si no más bien a nivel general existen varias herramientas de desarrollo que te pueden ayudar en ésta tarea siendo geniales la gran mayoría y fáciles de usar, como puede ser Spring (Java), Flask (Python) y NodeJS (JavaScript), de los más destacados.

Lo que trato de definir es que nos centraremos más en la parte técnica de lo que significa definir una buena API, siguiendo una correcta estructura, nomenclatura y un perfecto uso de los métodos Web implicados en dicho proceso. Que definir un Endpoint sea de las cosas más sencillas hoy en día de la programación Web no significa que pueda elaborarse de cualquier manera y sin respetar ciertas normas. Y el caso que me ha inspirado a escribir este artículo (Cuando buscando un poco existe cientos de información al respecto) es que todavía sigo viendo muchas APIs con un diseño pobre en estos aspectos que son los que realmente denotan la excelencia y el mimo en el producto que hay debajo. Estas técnicas nos permitirán con ello alcanzar la maestría en el desarrollo de Servicios Web, como siempre recordaros que todo el código de ejemplo mostrado se encuentra en el repositorio de código del sitio en GitHub, sin enrollarme más, ¡¡Comenzamos!! 😀

¿Que significa API REST?

Seguramente si estás empezando a moverte por el mundo del desarrollo Web (o si ya llevas tiempo en él también 😉 ), habrás leído, escuchado o mencionado cientos de veces la conjunción de ciertas palabras como son API, REST, RESTful, URL, Web Service, Endpoint… etc. Todas estas palabras que parecen una cosa totalmente distinta cada una; y que según la persona de turno te dirá una de ellas o una unión aleatoria de dos o más de estos vocablos según le convenga, sí que en su propia existencia representan una cosa distinta una de otra, pero entre todas ellas existe un fuerte nexo de unión, y este sería el protocolo HTTP.

HyperText Transfer Protocol (HTTP), es la definición del protocolo de comunicación que ha sido el encargado de habilitar la interconexión entre distintos servicios a través de Internet, y por lo tanto también es el padre de todos los elementos anteriormente mencionados. Ideado originalmente por Tim Berners-Lee allá por el año 1989, hoy en día se encuentra en manos del W3C, debido a esto podemos afirmar con total seguridad que se trata de un estándar de comunicación y que dio paso al entramado del universo Web que hoy en día conocemos.

Pues partiendo de éste esqueleto nacen el conjunto de estándares y tecnologías mencionadas, donde entre ellas se encuentra el concepto de API REST, y que enumeraremos a continuación:

  • URL (Uniform Resource Locator): Forma de identificar de forma unívoca un recurso Web. De esta forma solicitas cierta información o acción a un servicio Web.
    • Suele seguir una estructura comenzando por el protocolo de comunicación; (HTTP en el ejemplo) mediante el que interactuaremos con el servicio web, seguido del dominio; utilizado para especificar el servicio web en concreto con el que queremos establecer una comunicación (En el ejemplo se indica este mismo sitio web), pasamos a la ruta o path; con el que también distinguimos el recurso en concreto que queremos que nos ofrezca el sitio indicado (Para el ejemplo la ruta nos lleva a la categoría de artículos sobre tecnología dentro de este sitio), y por ultimo nos encontramos con los filtros de búsqueda de la URL utilizados como su propio nombre indica para filtrar dentro de todos los artículos que nos ofrece la ruta.
  • Web Service: Se define un servicio web como cualquier producto software que es capaz de ofrecer sus servicios a través de Internet.
  • EndPoint: Representa un extremo en un canal de comunicación. Cuando una API interactua con otro sistema los puntos de comunicación entre ellas se consideran los endpoints. Un endpoint también podría considerarse como la URL de un servicio Web.
  • API (Application Programming Interface): Consiste en el conjunto de recursos y protocolos con los que se basa el desarrollo e integración de aplicaciones software, también define el cómo deberían de comunicarse entre ellas, es decir, el conjunto de reglas de nombrado y protocolos que debe de seguir para un correcto y mutuo entendimiento. El más común suele ser mediante el protocolo HTTP y usando el estándar REST.
  • REST (REpresentational State Transfer): Se trata de un estándar de comunicación entre Servicios Web, y como todo estándar se compone de una serie de reglas. La unidad básica de información con la que trabaja REST, es con los recursos, entendiendo como recurso cualquier documento, imagen, representaciones de objetos (Por ejemplo, Persona) u colecciones de estos mismos. Dicho esto REST utiliza identificadores únicos por cada recurso con el que responde de forma inequívoca a las peticiones del cliente, y además el estado de dicho recurso en cualquier momento que se solicita se conoce como la Representación del Recurso.

Vistos de forma separada todos estos conceptos creo que ya tendríamos un poco más claro sobre qué nos referimos cuando se utiliza cada uno de ellos, ya sea de forma compuesta o individualmente. Y ahora que ya tenemos una idea sobre lo que significa el desarrollar e implementar una API REST, vamos a indagar un poquito más sobre todas las implicaciones y normas en las que se basa.

Métodos HTTP

Dentro del protocolo HTTP se definen una serie de posibles llamadas que se utilizan para comunicarnos con un servidor web, todas estas acciones simplemente se basan en la clara identificación de distintas acciones sobre un mismo recurso. Cada uno de estos métodos implementan una semántica diferente, aunque el contenido muchas veces siga siendo el mismo lo que cambia es la acción a realizar.

Pues bien, si aplicamos las directrices que marca REST en el uso de estas llamadas HTTP, tan solo se especifica que siempre utilicemos el mismo tipo de llamada HTTP para realizar las mismas acciones, ya que a nivel de programación cualquiera podría utilizar estas herramientas para elaborarlas a su propio gusto e interpretación de lo que deberían hacer, es por esto por lo que REST define este conjunto de normas, para que cualquier programador al que le expongas tu servicio tenga claro conceptualmente que es lo que está realizando en la integración de tu servicio en su sistema, y no lleve algún que otro quebradero de cabeza en el proceso.

Con lo que de todas estas llamadas, podríamos identificar un subconjunto básico de estas que detallaremos a continuación:

GET

Las peticiones GET suelen utilizarse para solicitar o pedir información al servicio web, es decir, visualizar la representación de un recurso. De ningún modo estas llamadas deberían de utilizarse para modificar la información del recurso en sí, osea se, al terminar esta llamada por parte del servidor, en éste no debería de haberse producido ningún cambio. Es más, esta llamada debería ser Idempotente, lo que viene ser la obtención del mismo resultado siempre que se realice la misma llamada, por lo que la información de dos peticiones GET seguidas deberían de ofrecer el mismo objeto devuelto.

Ejemplo en .Net:

    [ApiController]
    [Route("[controller]")]
    public class VehicleController : ControllerBase
    {
        private static readonly Vehicle[] Vehicles = new Vehicle[]
        {
            new Vehicle(){
                ID = 4,
                Color = "White",
                Marca = "Wolkswagen",
                Model = "Polo"
            },
            new Vehicle(){
                ID = 8,
                Color = "Blue",
                Marca = "Seat",
                Model = "Ibiza"
            },
            new Vehicle(){
                ID = 19,
                Color = "Red",
                Marca = "Toyota",
                Model = "Corolla"
            }
        };

        private readonly ILogger<VehicleController> _logger;

        public VehicleController(ILogger<VehicleController> logger)
        {
            _logger = logger;
        }
        
         //HTTP GET Endpoint definition
        [HttpGet("{id}")]
        public ActionResult<IEnumerable<Vehicle>> Get(int id)
        {
            var vehicle = Vehicles.FirstOrDefault(v => v.ID == id);
            return Ok(vehicle);
        }
    }

En el ejemplo de código anterior habríamos definido un Controller denominado VechicleController.cs en un proyecto ASP.NET Core. Donde definiremos una llamada HTTP GET en la que devolveremos un objeto de tipo Vehicle.cs filtrado por su ID. Este sería un ejemplo claro de petición de un recurso a una API REST. Si realizamos tal llamada en el navegador obtendremos un resultado como el siguiente:

Como podemos ver la obtención del recurso muestra su representación en formato JSON, por defecto en los navegadores. Se que quizá voy un poco rápido en el código elaborado y no entrando en demasiado detalle, pero en verdad la forma de definir una API con ASP.NET es muy sencillo, además de que el objetivo de éste artículo más que centrarnos en cómo funciona una tecnología específica vamos entraremos más en cómo definir y hacer las cosas de forma correcta independientemente de la tecnología subyacente. El mostrar código es simplemente a modo ilustrativo y para aportar mayor claridad.

POST

Este tipo de llamadas solamente deberían de utilizarse para crear nuevos recursos que no existiesen ya en la aplicación. Normalmente estos recursos forman parte de una colección de éste mismo tipo, además en modo de respuesta existen dos posibles variantes:

  1. Devolver el recurso creado con información válida.
  2. Devolver únicamente un código HTTP de respuesta correcta.

Es decir, o devolvemos simplemente el estado del resultado de la operación, o además de eso le mostramos el objeto que ha sido creado. En siguientes publicaciones entraré más en profundidad en los tipos de respuesta en una API REST. Una cuestión también a tener en cuenta es que esta llamada al contrario que con HTTP GET, no es idempotente, lo que significa que con cada llamada que realizamos crearemos un objeto distinto con un ID único.

Ejemplo en .Net:

VechicleController.cs

        [HttpPost]
        public ActionResult<Vehicle> Create(VehicleDto createVehicleDto)
        {
            var vehicle = new Vehicle()
            {
                Color = createVehicleDto.Color,
                Marca = createVehicleDto.Marca,
                Model = createVehicleDto.Model
            };

            Vehicles.Add(vehicle);

            return Ok(vehicle);
        } 

    public class VehicleDto
    {
        public string Model { get; set; }
        public string Color { get; set; }
        public string Marca { get; set; }
    }

Vehicle.cs

    public class Vehicle
    {
        public Vehicle()
        {
            // This is not the best way to generate auto-IDs. Just to simplify the example
            ID = new Random().Next(int.MaxValue);
        }

        public int ID { get; set; }
        public string Model { get; set; }
        public string Color { get; set; }
        public string Marca { get; set; }
    }

Extendemos la clase VehicleController.cs para añadirle la llamada POST, en la que como he descrito previamente crearemos un nuevo recurso de tipo Vehicle.cs y lo añadimos en la colección definida en el mismo controlador que representa una persistencia a nivel de BD. Lo habitual también en éste tipo de peticiones es que los parámetros necesarios para poder crear el recurso se envíen en el body o cuerpo de la llamada HTTP. Además si nos fijamos en la clase que representa el modelo de datos Vehicle.cs se ha definido para que con cada instancia nueva se genere un identificador único, así que con cada petición POST estaremos creando un nuevo recurso de éste mismo tipo aunque los datos que contenga sean exactamente los mismos. Si realizamos esta llamada nueva obtendremos un resultado como el siguiente:

Como se puede apreciar claramente he optado por la opción de retornar el nuevo recurso creado, en donde también vemos que se nos ha asignado un identificador único para este nuevo recurso. Además la propia URL generada afecta a la colección completa de vehículos sin especificar el identificador del recurso, puesto que aún no disponemos de éste dato.

Nota: Por la facilidad de poder realizar peticiones a nuestra Web API desarrollada he implementado Swagger, ya que para peticiones GET si que es posible realizarlas a través del navegador, pero para los otros tipos necesitamos de algunas herramientas más específicas, como pueden ser Swagger; implementado en la propia aplicación, Postman, ApiRequest.io…etc

PUT

En cambio este tipo de llamada se utiliza principalmente para actualizar información sobre un recurso existente. Pero realmente esta norma no es realmente estricta, ya que, si el recurso no existiese no estaría mal diseñado que la API decidiese crearlo en estos casos sin pasar por tener que realizar una llamada POST directamente.

Ejemplo en .Net:

        [HttpPut("{id}")]
        public ActionResult Upsert(int id, [FromBody]VehicleDto vehicleDto)
        {
            var existingVehicle = Vehicles.FirstOrDefault(v => v.ID == id);

            if(existingVehicle is null)
            {
                var vehicle = new Vehicle()
                {
                    Color = vehicleDto.Color,
                    Marca = vehicleDto.Marca,
                    Model = vehicleDto.Model
                };

                Vehicles.Add(vehicle);
            }
            else
            {
                existingVehicle.Color = vehicleDto.Color;
                existingVehicle.Marca = vehicleDto.Marca;
                existingVehicle.Model = vehicleDto.Model;
            }

            return Ok();
        }

Se que no estoy dedicando especial importancia a los códigos de retorno, pero tened paciencia, iremos perfeccionando la API de ejemplo creada conforme vayamos ampliando la explicación en posteriores secciones 😉

La implementación es realmente sencilla y tan solo consiste en buscar el recurso para actualizar sus campos (Obviamente esta parte puede ser mucho más compleja, ya que en el ejemplo estaríamos machacando siempre todos los datos del recurso), y si el recurso no es encontrado, en este caso yo he optado por la opción de crearlo, pero como ya comenté anteriormente esta parte es totalmente opcional. Por último, otro aspecto a destacar con la diferencia del método POST es que éste aplica sus cambios sobre la colección de recursos cuando se forma la URL, en cambio para los métodos PUT se incluye el recurso específico por identificador del que queremos actualizar. Si realizamos la llamada anterior el resultado sería como el siguiente:

En éste caso no he mostrado ningún dato como respuesta, tan solo el código de retorno HTTP de respuesta correcta por defecto.

DELETE

Como su propio nombre indica esta llamada se utiliza para eliminar recursos dentro de la colección. Aunque a primera vista pueda parecer que dicha llamada entra en el grupo de las consideradas idempotentes, ya que reiteradas peticiones generan el mismo efecto puesto que el recurso se eliminará una vez y las siguientes no surtirán efecto en el sistema, realmente esta norma no se cumple, puesto que la primera petición que encuentre el recurso si que lo eliminará retornando un código de respuesta exitoso, mientras que las siguientes al no encontrarlo, lo normal es que respondan con un código de error, rompiendo así la norma.

Ejemplo en .Net:

        [HttpDelete("{id}")]
        public ActionResult Remove(int id)
        {
            var vehicle = Vehicles.FirstOrDefault(v => v.ID == id);

            Vehicles.Remove(vehicle);

            return Ok();
        }

La implementación es bastante sencilla pues bastaría con tan solo buscar el recurso que se nos indica en la colección y borrarlo de esta. Ejemplo de la llamada resultante anterior:

Como vemos retorna un código 200 indicando que ha sido borrado correctamente, y si ahora volvemos a realizar una petición de consulta sobre el mismo recurso obtendremos lo siguiente:

Como vemos la respuesta es un código 204, indicando que el elemento buscado no existe, y por lo tanto la operación DELETE ha funcionado correctamente.

Nota: Existen muchos más tipos de llamadas HTTP que pueden utilizarse como son HEAD, OPTIONS, TRACE…etc. Pero para definir un servicio REST básico tan solo con los métodos anteriormente vistos es más que suficiente, es por esto que los omito para simplificar la explicación.

Convenciones de nombrado

En lo que llevamos de artículo ha salido varias veces la palabra recurso (En concreto unas 35 contando esta 😛 ), y es que resulta tan importante este concepto en el diseño de una API REST que para éste estándar es considerado la forma primaria de representar un dato. Por esto, escoger un buen nombre para nuestros recursos es una tarea importante aunque no lo parezca a simple vista, y es por lo que las reglas de nombrado se merecen una sección completa. Ya que si un recurso esta bien nombrado, será fácil de entender y de usar nuestras APIs REST.

Comenzaremos por lo básico, un recurso puede representar una colección de datos o por otro lado ser un ente único, siguiendo con el mismo ejemplo:

/vehicle      // Recurso único

/vehicles     // Colección de recursos

/vehicles/{vehicleId}   // Recurso único que forma parte de una colección

Este tipo de nombrado es denominado Uniform Resource Identifier (URI), donde el concepto sería algo parecido a una URL pero en éste caso una URI es sólo a nivel de recurso dentro de una API REST. Además cada colección puede contener sub-colecciones de elementos siguiendo las mismas normas:

/vehicles/{vehicleId}/drivers   // Colección de conductores para el recurso vehicle

/vehicles/{vehicleId}/drivers/{driverId}  // Conductor específico para un vehículo 

Lo normal además es que los recursos sean sustantivos, en lugar de verbos, ya que representan cosas no acciones. Estas mismas acciones vienen implícitas cuando aplicamos los métodos HTTP (GET, POST, PUT… etc) sobre un recurso que es un nombre. Existen casos típicos que nos sirven de ejemplo, como pueden ser:

/customers  // Clientes del sistema

/users/{userId}  // Usuario específico de un sistema

/accounts   // Cuentas de usuario.... etc

Si bien antes comentaba que un recurso se identifica con un sustantivo y que los verbos vendrían implícitos con las acciones HTTP que pudiéramos realizar sobre éste, existen ciertas excepciones al formar una URI por la que podríamos indicar un verbo. Y esta sería para indicar una acción específica sobre recurso en concreto, puesto que mediante las llamadas HTTP tan solo abarcaríamos acciones básicas tipo CRUD (Nunca especificar acciones de este estilo en la URI), y si bien es sabido que las aplicaciones son capaces de realizar muchas más acciones sobre los recursos que exponen. Estas llamadas normalmente suelen establecerse como una petición POST o PUT, dependiendo de la modificación que realizan en el sistema. Por ejemplo:

/vehicles/{vehicleId}/buy    // Como acción para comprar un coche

/songs/{songId}/play     // Ejemplo para reproducir una canción

Otra acción que ya llevamos haciendo todo el artículo y que toma mayor relevancia en recursos complejos es el uso del carácter contrabarra (‘/’) para indicar una relación de jerarquía entre recursos. Muy importante para aportar una mayor legibilidad y comprensión en nuestras APIs REST.

Por último, cuando además de la colección de recursos en sí queremos aplicar filtros, cambios de orden y paginación, es recomendable utilizar los componentes HTTP de filtrado típicos. Este tipo de acciones suelen realizarse sobre atributos o propiedades ya conocidos sobre los recursos, como por ejemplo:

/vehicles?color=red   // Colección de vehículos filtrada por color rojo

/vehicles/{vechileId}/drivers?Nationality=Spain  // Conductores de un vehículo con 
                                                 // nacionalidad española

Ejemplo en .Net:

        [HttpGet]
        public ActionResult<IEnumerable<Vehicle>> Get([FromQuery]string color)
        {
            var vehicle = Vehicles.Where(v => v.Color == color);
            return Ok(vehicle);
        }

Para aplicar este mismo filtro en .Net bastaría con indicar el atributo “[FromQuery]” en el parámetro del método por el que queramos filtrar. Entonces la llamada del ejemplo anterior sería como la siguiente:

Como vemos el resultado nos muestra el único vehículo que hemos añadido a la colección de recursos de color rojo, y si además nos fijamos en la URI formada es exactamente igual a la del ejemplo visto.

Todas estas reglas y técnicas son las que harán que tus APIs expuestas sean claras y concisas para los demás desarrolladores que quieran integrarlas en su sistema.

Conclusiones

Con esta primera introducción aclaratoria de lo que significa una API REST, métodos típicos de HTTP relacionados, convenciones de nombrado para las URIs y todos los conceptos que rodean a este término, doy por finalizada ésta primera publicación acerca de cómo definir correctamente una buena API REST. Reconozco que me he dejado muchas cosas en el tintero y algunas otras he pasado muy por encima, pero no os preocupéis ya que más adelante pienso publicar una segunda parte aclarando y profundizando más en muchos aspectos que esta tecnología conlleva, como puede ser el correcto uso de los códigos de respuesta HTTP, tratamiento del versionado, aspectos del contenido multimedia y compresión de datos... y un largo etcétera de contenido y buenas prácticas que darían para varias publicaciones.

Poco más os puedo adelantar sin hacer mucho spoiler! 😉 Disfrutad del verano como bien se pueda y hasta la próxima developers!