¿Qué es un API Rest?

Lo primero que debemos de ver son los conceptos:

API

Imagina que para manejar un auto no cuentas con:

Un volante

Una palanca de velocidades

Tablero

Te imaginas tooodo lo que tendrías que hacer para poder echar a andar el auto, mover las llantas, para prender las direcciones conectar el cable, todo sería mucho más complicado. En qué nos ayuda el volante, la palanca de velocidades y el tablero?

Realmente el estos elementos saben como manipular las partes del auto para poder dar vuelta, cambiar la velocidad ó encender las luces y lo hacen de una manera que simplemente nosotros no necesitamos conocer a fondo como funciona el auto.

El trabajo de un API es similar, las empresas crean sus APIs donde ellos tienen toda la lógica de su negocio, y hay “clientes” que son quienes implementan ó consumen el API, que simplemente piden información a esta API y el API sin necesidad de que el cliente conozca toda la lógica para obtener esa información.

Uno de los ejemplo es el API de Facebook, donde se exponen varias funcionalidades de facebook en unas peticiones HTTP tipo REST.

REST

REST básicamente significa Representational State Transfer (Transferencia de Estado Representacional), no te apures a mi tampoco hasta la fecha me dice mucho su nombre, pero ahora lo vemos más simple.

Básicamente es un ESTILO de arquitectura, no es una ley ni un protocolo simplemente son consejos ó ideas que al implementarlas en nuestro server nos dictan que sigue un estilo REST, y estás son las carácteristicas de este estilo:

Pongamos el siguiente ejemplo:

API Diagram
  • Recursos
  • CRUD
  • Formato

Resources (recursos)

En el ejemplo anterior, cuáles son los recursos que se utilizan:

Podrían ser:

  • Ropa: La ropa la crea el dueño de la tienda, le asigna una imagen, talla, color, código y un precio.
  • Usuarios: Los usuarios registrados en tu base de datos
  • Ordenes de pago: El cliente puede al hacer una compra pagar por una prenda ó varias, al crearlas los datos que podría llevar son:
    • código de la prenda (SKU)
    • cantidad
    • monto por prenda
    • monto total
    • Dirección de envío
  • Envíos: La dirección del envío que cada usuario puede tener una ó mas.

Para cada recurso de tu API en el estilo REST se tiene un path es decir tu URL:

  • https://<tuURL>/ropa
  • https://<tuURL>/usuarios
  • https://<tuURL>/usuario/<id>/pagos
  • https://<tuURL>/usuario/<id>/direcciones

Como puedes ver hay una diferente:

  • https://<tuURL>/usuario/<id>/direcciones
  • https://<tuURL>/usuario/<id>/pagos

Y es que direcciones y pagos tienen dueño, corresponden a un usuario, si bien

  • https://<tuURL>/usuarios

Te deja consultar todos los usuarios, esto:

  • https://<tuURL>/usuario/<id>

Te dejara consultar solo un usuario y esto:

  • https://<tuURL>/usuario/<id>/<nombre del recurso>

Todos los recursos asociados a un usuario. Entonces:

  • https://<tuURL>/usuario/1005/pagos

Tendría todos los pagos del usuario 1005

CRUD (Create-Update-Delete)

Cada recurso, por ejemplo en este caso ropa, puede CREARSE, ACTUALIZARSE y ELIMINARSE en la base de datos.

Para esto utilizamos los métodos del protocolo HTTP.

El protocolo HTTP es parte de nuestro día a día, cada que consultamos una página de internet en nuestro navegador utilizamos el protocolo HTTP y el método GET, no me crees?

Abre tu terminal y ejecuta el comando

curl www.google.com con esto ejecutaremos el protocolo HTTP y el método get de la url de google.

verás el montón de código, que arroja la terminal, lo reconoces? ES HTML!! y no es que una página sea REST, lo que pasa es que el método GET del protocolo HTTP regresa un archivo HTML para esté caso

Google PAGE

El protocolo HTTP y sus métodos en un estilo REST deben de utilizarse de la siguiente manera

Aún hay más pero estos son los más básicos:

  • POST para crear recursos
  • GET para consulta de recursos
  • PUT para actualizar TODOS los atributos de un recurso
  • PATCH para actualizar un recurso parcialmente, ósea no todos sus atributos.
  • DELETE para borrar recursos.

Como es un estilo no es ley que se utilicen así pero la gran mayoría lo utiliza así, algunos utilizan PUT para crear recursos en lugar de POST. Depende de tu diseño.

Formato

Si bien al ejecutar el comando

curl www.google.com

recibimos un archivo HTML, el protocolo HTTP puede regresar muchos formatos, video, música, pdf, html, json, xml, etc.

Para el estilo REST debe de regresar XML ó JSON algo así:

<persona id=1>
<nombre>Pepito Perez</nombre>
<telefono>5512345678</telefono>
</persona>
view raw persona.xml hosted with ❤ by GitHub
{
"id": 1,
"nombre": "Pepito Perez",
"telefono": 5512345678
}
view raw persona.json hosted with ❤ by GitHub

Ambos representan el recurso persona, pero como puedes ver el formato es distinto, depende también para que lo utilices y cuál sea tú diseño.

Repaso antes de lo práctico

En general el estilo REST define que debemos crear recursos en forma de paths en nuestra URL, y usar los métodos HTTP generalmente POST, PUT, PATCH, GET y DELETE para crear CRUDs con formato JSON ó XML.

Ejemplo:

recurso: Ropa

path: https://<URL>/ropa

Consulta

Todas las ropas:

GET

https://<URL>/ropa

Una Ropa por identificador

GET

https://<URL>/ropa/<ID>

CRUD:

Crear Ropa (POST):

https://<URL>/ropa

{ body (JSON) }

Editar Ropa (PUT):

https://<URL>/ropa/<ID>

{ body (JSON) }

Eliminar Ropa (DELETE):

https://<URL>/ropa/<ID>

Práctico!

Para este ejemplo vamos a usar Spring Boot con kotlin y crearemos un API de personas, para este ejemplo NO HABRÁ bases de datos eso vendrá después.

API Diagram

Creando el proyecto

Podemos usar como IDE: Eclipse, STS ó IntelliJ en mi caso yo utilizo IntelliJ pero son los mismos pasos:

CreateSpringProject

Cuando termines de crear el proyecto deberías de poder ver un pom.xml

pom location

Debe de verse más ó menos así:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codetecuhtli</groupId>
<artifactId>apirest</artifactId>
<version>0.0.1</version>
<name>apirest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<spring-cloud.version>Hoxton.SR7</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub

Agrega esta dependencia a tu pom file de esta manera

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

Y compila para que puedas usar las dependencias.

Componentes Spring

Al crear web services en spring generalmente hay buenas prácticas que seguir, y estás con usar:

  • @Repository: Anotar una clase con @Repository, indicamos que esa clase tendrá la responsabilidad de guardar y obtener datos para el service, puede ser la conexión a una base de datos, llamadas a otros web services, etc.
  • @Service: Anotar una clase con @Service implica que esa clase puede inyectar @Repository en ella, un service en Spring tiene toda la lógica de negocio, llama a los repositories para obtener sus datos y hace operaciones con ellos a fin de que esa respuesta sea la final para el servicio web. Al service no le interesa de dónde saca @Repository al final, solamente los pide y los procesa.
  • @RestController: el @RestController tiene al service y es el encargado de exponer la lógica al cliente, aquí definimos los paths de la url y que método HTTP corresponde a que función del service.

Creando el @Repository

Como comentábamos el repositorio se encarga de obtener los datos, generalmente es una base de datos a la que se conecta ó un servicio web, contiene toda la lógica para obtener los datos de cualquier fuente.

@Repository
class PersonRepository {
companion object {
val people = mutableListOf(
Person(1,"Pepe", "5512345678"),
Person(2, "Maria", "5522334455"),
Person(3, "Beto", "5566774411"),
Person(4, "Ana", "5544332266")
)
}
fun people(): List<Person> = people
fun findPersonById(id: Long): Person? = people.find { it.id == id }
fun createPerson(person: Person): Person = person.apply {
id = (people.size + 1).toLong()
people.add(this)
}
fun updatePerson(id: Long, person: Person): Person? {
val personFound = findPersonById(id)
personFound?.let {
people.remove(it)
if (it.phone != person.phone){
it.phone = person.phone
}
if (it.name != person.name){
it.name = person.name
}
people.add(it)
}
return personFound
}
fun deletePersonById(id: Long): Boolean{
val person = findPersonById(id)
person?.let {
return people.remove(it)
}
return false
}
}

Para este caso tenemos que la fuente de nuestros datos son datos hardcode que en posteriores tutos haremos conexiones a SQL y NoSQL.

Creando el @Service

Un @Service tiene toda la lógica de negocio, puede tener uno ó más @Repository, intercambiará y procesará datos con el/los @Repository

interface PersonService {
fun getAllPeople(): List<Person>
fun getPersonById(id: Long): Person
fun createPerson(person: Person)
fun updatePerson(id: Long, person: Person): Person
fun deletePersonById(id: Long)
}

Generalmente se hace un contrato (un Interface) que en la implementación se la agrega la lógica, por si en algún caso otro controller necesita el mismo service con diferente lógica ú otros @Repository, la clase que implementa la interfaz del service generalmente acaba con impl.

Ahí inyecta al @Repository y lo utiliza para intercambiar datos

@Service
class PersonServiceImpl: PersonService {
@Autowired
private lateinit var personRepository: PersonRepository
override fun getAllPeople(): List<Person> {
return personRepository.people()
}
override fun getPersonById(id: Long): Person {
return personRepository.findPersonById(id) ?: TODO("throw not found exception")
}
override fun createPerson(person: Person) {
personRepository.createPerson(person)
}
override fun updatePerson(id: Long, person: Person): Person {
return personRepository.updatePerson(id, person) ?: TODO("throw not found exception")
}
override fun deletePersonById(id: Long) {
personRepository.deletePersonById(id)
}
}

En este caso no hay mucha lógica de negocio por lo que casi se llama directo, lo único que está sin implementar y por eso tiene los TODO son los notFound que deberíamos regresar un 404 pero eso lo veremos en otro tutorial para ver el manejo de errores en sprint.

Creando el @RestController

El @RestController define el uso del protocolo HTTP y de la url para el consumo del servicio, por ejemplo podemos definir que para personas el path será /person que el método POST llamara a la creación de una persona como lo vemos a continuación:

@RestController
@RequestMapping("person")
class PersonController {
@Autowired
private lateinit var personService: PersonService
@GetMapping
fun getPeople(): List<Person> =
personService.getAllPeople()
@GetMapping("/{id}")
fun getPersonById(@PathVariable("id") id: Long) =
personService.getPersonById(id)
@PostMapping
fun createPerson(@RequestBody person: Person) =
personService.createPerson(person)
@PatchMapping("/{id}")
fun updatePerson(@PathVariable("id") id: Long, @RequestBody person: Person) =
personService.updatePerson(id, person)
@DeleteMapping("/{id}")
fun deletePerson(@PathVariable("id") id: Long) =
personService.deletePersonById(id)
}

Explico para que tanta anotación:

  • @RestController: Configuramos esta clase como receptora de peticiones HTTP de los clientes.
  • @RequestMapping: En este caso decimos que el path para el CRUD de persona será /person 
  • @Autowired: Lo usamos para inyectar nuestro @Service
  • @GetMapping: Especificamos que el método GET del path raíz devolverá todas las personas es decir: GET /person
  • @GetMapping(“/{id}”): El método anotado con esto va a responder a la petición GET del path /person/<id> y devolverá la persona que le corresponde el id
  • @PostMapping: El método anotado con esto, responderá a la petición HTTP, método POST y con el path /person para crear una persona en la base de datos.
  • @PatchMapping(“/{id}”): Indica que el método anotado responderá a la petición HTTP, método Patch y path /person/<id> para actualizar el nombre ó el teléfono del recurso correspondiente al id ingresado en el path.
  • @DeleteMapping(“/{id}”): El método anotado con esto responderá a la petición HTTP, método Delete y el path /person/<id>, borrando la persona de nuestra lista en memoria.
  • @PathVariable(“id”): Debe de hacer match con lo definido en los mappings lo que va en {id}, el parámetro anotado con este será el que tendrá el valor ingresado en el path de la url.
  • @RequestBody: El parámetro anotado con esto será lo que se debe enviar en el body de los métodos: POST, PUT ó PATCH

Probemos el resultado!

Pongamos a correr nuestro programa! y hagamos lo que indica en la imagen para esto yo uso postman como cliente.

use api

En el siguiente post veremos el manejo de errores al final es tener una API completa como se usa en la industria!

MUCHAS GRACIAS!