Arquivo por categoria API

Criando um endpoint REST com ASP.NET Core



Neste post daremos continuidade a uma série de postagens sobre Web-Services divididos entre conceitos teóricos e praticos. Nos 3 primeiros posts focou-se em teoria com um sobre Web Services, outro sobre RESTful Web Services e mais um sobre os HTTP Status Codes mais comuns. Este post por sua vez complementa a parte teórica e o que foi desenvolvido na série de posts em que implementamos API’s REST com SpringBoot. Faremos aqui uma abordagem das mesmas situações só que dessa vez com a tecnologia ASP.NET Core. Além disso o código desse projeto se baseia no post anterior Criando um endpoint REST com ASP.NET Core. Então antes de começar confira o post Criando uma simples Web API RESTful em .NET Core 2.0.



Agora faremos algumas alterações no projeto original. Para começar crie os seguintes source folders Models, Services e dentro de Services Implementations. A estrutura da nossa arquitetura deve ficar similar a imagem abaixo.



Arquitetura da Aplicação



Feito isto crie uma classe chamada Person, no source folder Models, com as informações que serão representadas pelo nosso endpoint REST.

namespace SimpleRestfulAPIWithAspNetCore.Models
{
    public class Person
    {
        public long Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
    }
}


Feito isto crie uma interface com o nome IPersonService no source folder Services.


using SimpleRestfulAPIWithAspNetCore.Models;
using System.Collections.Generic;

namespace SimpleRestfulAPIWithAspNetCore.Services
{
    public interface IPersonService
    {
        Person Create(Person person);
        Person FindById(string personId);
        List<Person> FindAll();
        Person Update(Person person);
        void Delete(string personId);
    }
}



Agora vamos criar a implementação para a interface PersonService. No source folder Services –> Implementations crie a classe PersonServiceImpl que implementa a interface que acabamos de criar.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SimpleRestfulAPIWithAspNetCore.Models;
using System.Threading;

namespace SimpleRestfulAPIWithAspNetCore.Services.Implementations
{
    public class PersonServiceImpl : IPersonService
    {
        // Contador responsável por gerar um fake ID já que não estamos
        // acessando nenhum banco de dados
        private volatile int count;

        // Metodo responsável por criar uma nova pessoa
        // Se tivéssemos um banco de dados esse seria o
        // momento de persistir os dados
        public Person Create(Person person)
        {
            return person;
        }

        // Método responsável por retornar uma pessoa
        // como não acessamos nenhuma base de dados
        // estamos retornando um mock
        public Person FindById(string personId)
        {
            return new Person
            {
                Id = IncrementAndGet(),
                FirstName = "Leandro",
                LastName = "Costa",
                Address = "Uberlândia - Minas Gerais - Brasil"
            };
        }

        // Método responsável por retornar todas as pessoas
        // mais uma vez essas informações são mocks
        public List<Person> FindAll()
        {
            List<Person> persons = new List<Person>();
            for (int i = 0; i < 8; i++)
            {
                Person person = MockPerson(i);
                persons.Add(person);
            }
            return persons;
        }

        // Método responsável por atualizar uma pessoa
        // por ser mock retornamos a mesma informação passada
        public Person Update(Person person)
        {
            return person;
        }

        // Método responsável por deletar
        // uma pessoa a partir de um ID
        public void Delete(string personId)
        {
            //A nossa lógica de exclusão viria aqui
        }

        // Método responsável por mockar uma pessoa
        private Person MockPerson(int i)
        {
            return new Person
            {
                Id = IncrementAndGet(),
                FirstName = "Person Name " + i,
                LastName = "Last Name " + i,
                Address = "Some Address in Brasil " + i
            };
        }
        
        public Int32 IncrementAndGet()
        {
            return Interlocked.Increment(ref count);
        }
    }
}



Por fim renomeie a classe ValuesController para PersonController, após as alterações esse será o controller responsável por expor o endpoint de pessoas, no source folder Controllers.

using Microsoft.AspNetCore.Mvc;
using SimpleRestfulAPIWithAspNetCore.Models;
using SimpleRestfulAPIWithAspNetCore.Services;

namespace SimpleRestfulAPIWithAspNetCore.Controllers
{
    /* Mapeia as requisições de http://localhost:{porta}/api/person/
    Por padrão o ASP.NET Core mapeia todas as classes que extendem Controller
    pegando a primeira parte do nome da classe em lower case [Person]Controller
    e expõe como endpoint REST
    */
    [Route("api/[controller]")]
    public class PersonController : Controller
    {
        //Declaração do serviço usado
        private IPersonService _personService;

        /* Injeção de uma instancia de IPersonService ao criar
        uma instancia de PersonController */
        public PersonController(IPersonService personService)
        {
            _personService = personService;
        }

        //Mapeia as requisições GET para http://localhost:{porta}/api/person/
        //Get sem parâmetros para o FindAll --> Busca Todos
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(_personService.FindAll());
        }

        //Mapeia as requisições GET para http://localhost:{porta}/api/person/{id}
        //recebendo um ID como no Path da requisição
        //Get com parâmetros para o FindById --> Busca Por ID
        [HttpGet("{id}")]
        public IActionResult Get(string id)
        {
            var person = _personService.FindById(id);
            if (person == null) return NotFound();
            return Ok(person);
        }

        //Mapeia as requisições POST para http://localhost:{porta}/api/person/
        //O [FromBody] consome o Objeto JSON enviado no corpo da requisição
        [HttpPost]
        public IActionResult Post([FromBody]Person person)
        {
            if (person == null) return BadRequest();
            return new ObjectResult(_personService.Create(person));
        }

        //Mapeia as requisições PUT para http://localhost:{porta}/api/person/
        //O [FromBody] consome o Objeto JSON enviado no corpo da requisição
        [HttpPut]
        public IActionResult Put([FromBody]Person person)
        {
            if (person == null) return BadRequest();
            return new ObjectResult(_personService.Update(person));
        }

        //Mapeia as requisições DELETE para http://localhost:{porta}/api/person/{id}
        //recebendo um ID como no Path da requisição
        [HttpDelete("{id}")]
        public IActionResult Delete(string id)
        {
            _personService.Delete(id);
            return NoContent();
        }
    }
}



Agora que terminamos nossa codificação inicie a aplicação. Para testar nosso endpoint e cada uma de suas operações usaremos o plugin do Chrome Postman.


Primeiro faremos uma requisição do tipo GET chamando a operação findAll para o recurso http://localhost:{porta}/api/person/. Como se pode ver na imagem abaixo obtemos como resposta um JSON com um array de pessoas.



Requisição REST do tipo GET



Agora faremos uma requisição do tipo GET chamando a operação http://localhost:{porta}/api/person/1 passando como variável path o ID da pessoa que desejamos recuperar. Da mesma forma que na requisição anterior recebemos como resposta um JSON com 1 objeto pessoa.



Requisição REST do tipo GET passando variavel via PATH

Agora faremos uma requisição do tipo POST (esse é o verbo REST usado para persistir informações) chamando a operação http://localhost:{porta}/api/person/ passando como parâmetro um JSON com um objeto pessoa no corpo da requisição. Para isso no Postman precisamos setar o tipo de requisição como POST, na aba Body definimos o JSON que representa o objeto pessoa a ser gravado, selecionamos a opção raw, definimos a opção JSON(application/json) e por fim executamos a request através do botão send.



Requisição REST do tipo POST gravando um recurso


Agora faremos uma requisição do tipo PUT (esse é o verbo REST usado para atualizar informações) chamando a operação http://localhost:{porta}/api/person/ passando como parâmetro um JSON com um objeto pessoa no corpo da requisição. Lembrando que essa representação de pessoa deve ter um ID que será usado pra localizar e atualizar um recurso na base. Para isso no Postman precisamos setar informações similares ao post a unica diferença é que o tipo de requisição deve ser o PUT.



Requisição REST do tipo PUT atualizando um recurso



Por fim faremos uma requisição do tipo DELETE (verbo REST usado excluir informações) chamando a operação http://localhost:{porta}/api/person/1 passando como variável path o ID da pessoa que desejamos remover da base. Esse tipo de requisição retorna como resposta um body vazio e um StatusCode 200 caso a operação seja executada com sucesso os StatusCode 204 (no content), 401 (Unauthorized), 403 (forbiden), 404 (not found) ou 500 (internal server error) podem ser retornados.



Requisição REST do tipo DELETE removendo um recurso



Dessa forma abordamos os 4 verbos principais do HTTP usados em aplicações REST. Continue ligado no blog, por que no próximo post iremos documentar a nossa API com o framewrok Swagger. E claro a abordagem nesses posts também será totalmente mão na massa. É isso aí bons estudos.

Tags: , , , ,

Criando uma simples Web API RESTful em .NET Core 2.0



Fala rapazeada nesse post criaremos uma pequena API RESTful totalmente do zero. Para isso no Visual Studio clique em Arquivo → Novo → Projeto ou simplesmente digite CTRL+Shift+N.



Downloading Installer



Na tela a seguir selecione Visual C# → .NET Core → Aplicativo Web ASP.NET Core, defina o nome da sua solução no meu caso SimpleRestfulAPIWithAspNetCore e o local em que o nosso código será salvo e clique em OK.



Downloading Installer



Em seguida selecione API Web e clique em OK. Nesse ponto certifique-se de que .NET Core e ASP.NET Core 2.0 estejam selecionados.



Downloading Installer



Pronto nossa API já foi criada, se você for até o Gerenciador de Soluções verá algo muito parecido com a imagem abaixo.

Downloading Installer

Por padrão o Visual Studio cria as classes Program.cs e Startup.cs sendo que a primeira é a classe principal da aplicação e a segunda é onde definimos as configurações de HTTP e dos serviços a serem adicionados ao container. Na pasta Controllers temos a classe ValuesController.cs que é um template inicial de um serviço REST com as operações GET, POST, PUT e DELETE.


using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace SimpleRestfulAPIWithAspNetCore.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}



Apenas isso é o suficiente para criar um primeiro endpoint básico. Se executarmos nossa aplicação e abrirmos o browser na seguinte URL http://localhost:53032/api/values nossa aplicação retornará o valor [“value1″,”value2”] que é o retorno do nosso endpoint GET. Entretanto o REST “canônico” determina que uma verdadeira aplicação RESTful deve retornar os status code corretos para cada operação. Se quiser uma explicação mais detalhada sobre HTTP Status Codes em Serviços REST confira esta postagem e para entender mais sobre RESTful Web Services confira esta postagem aqui. Os status code mais comumente usados são:


  • 200 – Requisição executada com sucesso (OK);
  • 201 – Recurso criado com sucesso;
  • 202 – A requisição de atualização foi aceita e será processada (embora possa ser rejeitada);
  • 204 – Requisição processada e não há conteúdo de retorno.



Além disso, as respostas às requisições RESTful às vezes contêm informações:


  • 200OK – se requisição for GET, a resposta conterá um objeto (ou lista de objetos).
  • 201Created – a resposta conterá o objeto que foi criado e também o URI exclusivo necessário para obter esse objeto caso a API tenha suporte a HATEOAS. Para entender melhor sobre HATEOAS confira esta postagem aqui explicando a teoria por trás desse conceito e esta aqui demonstrando na prática.
  • 202Accepted – a resposta conterá o objeto para o qual uma atualização foi solicitada.
  • 204No content – isso pode ser retornado como resultado de um pedido de exclusão, onde não faria sentido retornar um objeto (já que teoricamente não existe mais). Para alguns teóricos mais puristas uma API não deve retornar 204 ( No content), ela deve ajudar o cliente e indicar lugares para ir. Um exemplo de URL a se fornecer é a URL da listagem de recursos de onde o cliente acabou de excluir um recurso, talvez ele deseje excluir mais recursos.



Levando em conta os Status Code RESTful destacados acima e de acordo com Jeremy Lindsay o ValuesControler deveria ser mais útil e prover uma implementação que atenda a essas especificações. Sendo assim faremos algumas mudanças no nosso serviço.


using Microsoft.AspNetCore.Mvc;

namespace SimpleRestfulAPIWithAspNetCore.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(new string[] { "value1", "value2" });
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            return Ok("value");
        }

        // POST api/values
        [HttpPost]
        public IActionResult Post([FromBody]string value)
        {
            return Created($"api/Values/{value}", value);
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public IActionResult Put(int id, [FromBody]string value)
        {
            return Accepted(value);
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            return NoContent();
        }
    }
}



As principais mudanças feitas são:


  • O tipo de retorno de cada ação é agora IActionResult, que permite que os HTTP Status Code sejam retornados corretamente.
  • Para as requisições GET, empacotamos retornos (que são strings simples) com um OK result.
  • Para a requisição POST, retornamos o objeto criado.
  • Para a requisição PUT, empacotamos o objeto retornado com o objeto Aceito para atualização.
  • Finalmente, para a ação DELETE, em vez de retornar vazio, devolvemos um tipo de resultado de NoContent.

É isso aí bons estudos e se gostou da postagem vá até o topo dessa página e torne-se um membro VIP para receber conteúdos exclusivos.

Tags: , , , ,

Aplicações RESTful HATEOAS com SpringBoot


O HATEOAS, é uma das principais constraints arquiteturais do REST e possibilita a navegação entre recursos, que são representações dos modelos de negócio da aplicação. Esse post é bem mão na massa e se quiser aprofundar um pouco mais em conceitos teóricos de HATEOAS confira o nosso post Entendendo HATEOAS. Vamos tomar como base a aplicação desenvolvida no nosso outro post Documentando aplicações REST com SpringBoot e Swagger. Para começar você pode baixar o código deste post aqui e descompactar o arquivo zip e importar na sua IDE preferida ou clonar usando Git:


git clone https://github.com/leandrocgsi/simple-rest-example-swagger.git


Primeiro altere o pom.xml adicionando os trechos destacados por comentários abaixo.


<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>br.com.erudio</groupId>
    <artifactId>simple-rest-example-hateoas</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
    </parent>
    
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- Adicione a dependencia de HATEOAS-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-hateoas</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.mangofactory</groupId>
            <artifactId>swagger-springmvc</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.ajar</groupId>
            <artifactId>swagger-spring-mvc-ui</artifactId>
            <version>0.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        
        <!-- Adicione as dependencias de teste -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <scope>test</scope>
        </dependency>        
        
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
        <repository>
            <id>jcenter-release</id>
            <name>jcenter</name>
            <url>http://oss.jfrog.org/artifactory/oss-release-local/</url>
        </repository>
    </repositories>
    
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
    
</project>


Primeiro vamos alterar a classe Greeting que agora ira extender ResourceSupport.


package br.com.erudio.models;

import org.springframework.hateoas.ResourceSupport;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Greeting extends ResourceSupport {

    private final long idGreeting;
    private final String content;

    @JsonCreator
    public Greeting(@JsonProperty("id") long id, @JsonProperty("content") String content) {
        this.idGreeting = id;
        this.content = content;
    }

    public long getIdGreeting() {
        return idGreeting;
    }

    public String getContent() {
        return content;
    }
}


Agora adicione os trechos comentados a classe GreetingController.


package br.com.erudio.web.controllers;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;

import br.com.erudio.models.Greeting;

@Api(value = "greeting")
@RestController
@RequestMapping("/greeting")
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

	@ApiOperation(value = "Show Greeting Message" )
	@ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public HttpEntity<Greeting> greeting(@RequestParam(value="name", defaultValue="World") String name) {
		Greeting greeting = new Greeting(counter.incrementAndGet(), String.format(template, name));
		
		// Na prática essa linha adiciona uma auto referência ao próprio endpoint
		// e apenas esse pequeno trecho de código já é o suficiente para que o endpoint 
		// greeting seja HATEOAS
		greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
        
		return new ResponseEntity<Greeting>(greeting, HttpStatus.OK);
    }
}


Se você iniciar a aplicação e acessar o endereço localhost:8080/greeting verá algo similar a imagem abaixo.

postagem_4_0


package br.com.erudio.models;

import java.io.Serializable;

import org.springframework.hateoas.ResourceSupport;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

// Adicione a anotação @JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)

//Extenda ResourceSupport
public class Person extends ResourceSupport implements Serializable{
    
    private static final long serialVersionUID = 1L;
    
	// Por padrão implementação HATEOAS do Spring tem um atributo id
	// como default por isso o ID de nossa entidade deve ser alterado
    private Long idPerson;
    private String firstName;
    private String lastName;
    private String address;
    
    public Person() {}

	public Long getIdPerson() {
        return idPerson;
    }
    
    public void setIdPerson(Long id) {
        this.idPerson = id;
    }
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
}


Agora vamos alterar a classe PersonController:


package br.com.erudio.web.controllers;
 
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
 
import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;
 
@Api(value = "person")
@RestController
@RequestMapping("/person/")
public class PersonController {
     
    @Autowired
    private PersonService personService;
	@ApiOperation(value = "Find person by ID" )
    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public Person get(@PathVariable(value = "personId") String personId){
        Person person = personService.findById(personId);
		
		//Adicione uma auto referencia ao método get do controller passando o ID como parâmetro
        person.add(linkTo(methodOn(PersonController.class).get(personId)).withSelfRel());
		return person;
    }
     
    .
    .
    .
     
}


Note que ao acessar esse recurso com uma ferramenta como o POSTman teremos uma resultado similar a imagem abaixo.


postagem_4_1


Agora vamos alterar o findAll:


package br.com.erudio.web.controllers;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;

import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;

@Api(value = "person")
@RestController
@RequestMapping("/person/")
public class PersonController {
	
	@Autowired
	private PersonService personService;
	
	.
	.
	.
	
	@ApiOperation(value = "Find all persons" )
	@ResponseStatus(HttpStatus.OK)
	@RequestMapping(value = "/findAll", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	public List<Person> findAll(){
		List<Person> persons = personService.findAll();
		ArrayList<Person> personsReturn = new ArrayList<Person>();
		for (Person person : persons) {
			String idPerson = person.getIdPerson().toString();
			
			// Adicione uma referencia ao método get do controller passando o ID como parâmetro
			// isso é feito para todos os elementos da lista
			person.add(linkTo(methodOn(PersonController.class).get(idPerson)).withSelfRel());
			personsReturn.add(person);
		}
		return personsReturn;
	}
	
	.
	.
	.
	
}


A imagem abaixo nos mostra o resultado dessa mudança. A nossa lista nos tras referencias únicas para cada um dos recursos.


postagem_4_2


Agora vamos adicionar o suporte a HATEOAS ao verbo POST da nossa aplicação.


package br.com.erudio.web.controllers;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;

import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;

@Api(value = "person")
@RestController
@RequestMapping("/person/")
public class PersonController {
	
	@Autowired
	private PersonService personService;
	
	.
	.
	.
	
	@ApiOperation(value = "Create a new person" )
	@ResponseStatus(HttpStatus.OK)
	@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
	public Person create(@RequestBody Person person){
		Person createdPerson = personService.create(person);
		String idPerson = createdPerson.getIdPerson().toString();
		
		// Após criarmos um novo recurso do tipo Person nós recuperamos seu ID e adicionamos
		// uma referencia ao método get do controller passando o ID como parâmetro		
		createdPerson.add(linkTo(methodOn(PersonController.class).get(idPerson)).withSelfRel());
		return createdPerson;
	}
	
	.
	.
	.
	
}


Como se pode ver na imagem após salvar uma nova pessoa a aplicação retorna um link para que as informações da mesma possam ser acessadas.


postagem_4_3


Agora vamos modificar o verbo PUT.


package br.com.erudio.web.controllers;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;

import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;

@Api(value = "person")
@RestController
@RequestMapping("/person/")
public class PersonController {
	
	@Autowired
	private PersonService personService;
		
	.
	.
	.
	
	@ApiOperation(value = "Update an existing person")
	@ResponseStatus(HttpStatus.OK)
	@RequestMapping(method = RequestMethod.PUT,consumes = MediaType.APPLICATION_JSON_VALUE)
	public Person update(@RequestBody Person person){
		Person updatedPerson = personService.update(person);
		String idPerson = updatedPerson.getIdPerson().toString();
		
		// Após atualizarmos um recurso nós recuperamos seu ID e adicionamos
		// uma referencia ao método get do controller passando o ID como parâmetro		
		updatedPerson.add(linkTo(methodOn(PersonController.class).get(idPerson)).withSelfRel());		
		return updatedPerson;
	}
	
	.
	.
	.
	
}


Da mesma forma que com o verbo POST após atualizar uma pessoa a aplicação retorna um link para que as informações da mesma possam ser acessadas.


postagem_4_4


Como o verbo DELETE exclui um recurso não há necessidade de adicionar suporte a HATEOAS nele. Sendo assim fechamos a nossa implementação e podemos dizer que a nossa API é finalmente RESTful. Assim como nos posts anteriores você pode baixar o código deste post aqui e descompactar o arquivo zip e importar na sua IDE preferida ou clonar usando Git:

git clone https://github.com/leandrocgsi/simple-rest-example-hateoas.git


É isso aí bons estudos e continuem ligados no blog para mais novidades 😉


Tags: , , , , ,

Entendendo HATEOAS


HATEOAS (Hypermedia as the Engine of Application State) é uma constraint arquitetural de aplicações REST. Uma API HATEOAS provê informações que permite navegar entre seus endpoints de forma dinâmica visto que inclui links junto às respostas. Esta capacidade a difere de sistemas baseados em SOA e interfaces dirigidas por WSDL(pronuncia-se uísdou). Com SOA, servidores e clientes usualmente devem acessar uma especificação fixa que pode ser acessada em outro lugar na API, ou em um website, ou as vezes distribuído por email.


Nota: Pronunciar HATEOAS não é fácil e pode variar bastante. Algumas pessoas pronunciam algo como “riteos“, outros como “reitos” ou como “reidôs“. Alguns podem se referir a esse conceito arquitetural por hypermedia-driven system – algo como sistema dirigido por hipermídia.


Exemplos


O código a seguir representa um objeto Cliente.


class Cliente {
    String nome;
}


Uma representação JSON tradicional seria algo como:


{ 
    "nome" : "Leandro"
}


Os dados do Cliente estam lá, mas os dados não contém nada de relevante sobre suas conexões. Uma representação baseada em HATEOAS deve ser similar ao JSON abaixo:


{
    "nome": "Leandro",
    "links": [ {
        "rel": "self",
        "href": "http://localhost:8080/Cliente/1"
    } ]
}


A resposta não contém somente o nome de uma pessoa, mas inclui uma URL com o endereço de onde as informações dessa pessoa podem ser localizadas.
rel significa relacionamento. No nosso caso o link autoreferencia a pessoa. Sistemas mais complexos incluem outros tipos de relacionamentos. Por exemplo, uma ordem de compra pode ter um relacionamento com cliente”rel”:”Cliente” vinculando a ordem ao Cliente.
href é uma URL completa que define um único recurso.


Nota: Apesar de meus exemplos serem em JSON, XML também é aceito como um formato de resposta padrão. HATEOAS não impõe a exigência à um formato em específico. O seu foco é prover links que possibilite navegar entre os recursos de uma API.


Apesar de nossos exemplos serem bem simples é possível construir relações mais complexas. O HATEOAS, facilita aos desenvolvedores, de aplicações cliente, acessar os recursos de uma API sem precisar definir uma especificação, criar um documento externo ou uma wiki para isso.
Observe o JSON abaixo:


{
    "conteudo": [ {
        "preco": 499.00,
        "descricao": "HD Seagate 2TB",
        "nome": "HD S2TB",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/produto/1"
        } ],
        "atributos": {
            "conector": "SATA"
        }
    }, {
        "preco": 49.00,
        "descricao": "Mouse Óptico Dell",
        "nome": " Mouse",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/produto/3"
        } ],
        "atributos": {
            "conector": "wireless"
        }
    } ],
    "links": [ {
        "rel": "produto.consulta",
        "href": "http://localhost:8080/produto/consulta"
    } ]
}

Não apenas os itens e seus precos mostrados, mas também uma URL para cada recurso, fornecendo informações claras sobre como acessar cada recurso. De acordo com o modelo de maturidade de Richardson, HATEOAS é considerado o ultimo nível do REST. Isto significa que em uma aplicação HATEOAS presume-se que os verbos padrão de uma aplicação REST como GET, POST, PUT e DELETE também são adotados. Sendo que cada um deles, como é mostrado acima, provê ao cliente os links necessários ao acesso à uma informação.


É isso aí continuem ligados no blog e em breve teremos uma postagem bem mão na massa com HATEOAS e o nosso velho conhecido Spring Boot. Bons estudos 😉

Tags: , , ,

Criando o primeiro endpoint REST com SpringBoot



Neste post daremos continuidade a uma série de postagens sobre Web-Services divididos entre conceitos teóricos e praticos. Nos 3 primeiros posts focou-se em teoria com um sobre Web Services, outro sobre RESTful Web Services e mais um sobre os HTTP Status Codes mais comuns. Este post por sua vez complementa o que foi desenvolvido no post Do zero ao REST em 5 minutos com SpringBoot. Então antes de começar veja o post anterior ou baixe o código e vamos entender na prática como funcionam os verbos REST.



Agora faremos algumas alterações no projeto desenvolvido no post anterior. Para começar crie os seguintes pacotes br.com.erudio.models, br.com.erudio.services, br.com.erudio.services.implementations e br.com.erudio.web.controllers. Depois mova a classe Greeting para o pacote br.com.erudio.models e a classe GreetingController para o pacote br.com.erudio.web.controllers. A estrutura de pacotes deve ficar similar a imagem abaixo.

postagem_2_0



Feito isto crie uma classe chamada Person, no pacote br.com.erudio.models, com as informações que serão representadas pelo nosso endpoint REST.

package br.com.erudio.models;

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person implements Serializable{
    
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String firstName;
    private String lastName;
    private String address;
    
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
}


Feito isto crie uma interface com o nome PersonService no pacote br.com.erudio.models.


package br.com.erudio.services;

import java.util.List;

import br.com.erudio.models.Person;

public interface PersonService {
    
    Person create(final Person person);
    Person findById(final String personId);
    List<Person> findAll();
    Person update(Person person);
    void delete(final String personId);

}



Agora vamos criar a implementação para a interface PersonService. No pacote br.com.erudio.services.implementations crie a classe PersonServiceImpl que implementa a interface que acabamos de criar.

package br.com.erudio.services.implementations;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.stereotype.Service;

import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;

@Service
public class PersonServiceImpl implements PersonService {
    
	// Contador responsável por gerar um fake ID já que não estamos
	// acessando nenhum banco de dados
    private final AtomicLong counter = new AtomicLong();

	// Metodo responsável por criar uma nova pessoa
	// Se tivéssemos um banco de dados esse seria o
	// momento de persistir os dados
    @Override
    public Person create(Person person) {
        return person;
    }

	// Método responsável por retornar uma pessoa
	// como não acessamos nenhuma base de dados
	// estamos retornando um mock
    @Override
    public Person findById(String personId) {
        Person person = new Person();
        person.setId(counter.incrementAndGet());
        person.setFirstName("Leandro");
        person.setLastName("Costa");
        person.setAddress("Uberlândia - Minas Gerais - Brasil");
        return person;
    }

	// Método responsável por retornar todas as pessoas
	// mais uma vez essas informações são mocks
    @Override
    public List<Person> findAll() {
        ArrayList<Person> persons = new ArrayList<>();
        for (int i = 0; i < 8; i++) {
            Person person = mockPerson(i);
            persons.add(person);
        }
        return persons;
    }
    
	// Método responsável por atualizar uma pessoa
	// por ser mock retornamos a mesma informação passada
    @Override
    public Person update(Person person) {
    	return person;
    }

	// Método responsável por deletar
	// uma pessoa a partir de um ID
    @Override
    public void delete(String personId) {

    }
		
	// Método responsável por mockar uma pessoa
    private Person mockPerson(int i) {
        Person person = new Person();
        person.setId(counter.incrementAndGet());
        person.setFirstName("Person Name " + i);
        person.setLastName("Last Name " + i);
        person.setAddress("Some Address in Brasil " + i);
        return person;
    }
}



Por fim crie a classe PersonController, responsável por expor o endpoint de pessoas, no pacote br.com.erudio.web.controllers.

package br.com.erudio.web.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import br.com.erudio.models.Person;
import br.com.erudio.services.PersonService;

@RestController
//Mapeia as requisições de localhost:8080/person/
@RequestMapping("/person/")
public class PersonController {
    
    @Autowired
    private PersonService personService;
    
    @ResponseStatus(HttpStatus.OK) //Por padrão responde com o status code 200 success
    @RequestMapping(value = "/{personId}",
		//Mapeia as requisições GET para localhost:8080/person/
		//recebendo um ID como @PathVariable
		method = RequestMethod.GET, 
		produces = MediaType.APPLICATION_JSON_VALUE)
		// Produz JSON como retorno
    public Person get(@PathVariable(value = "personId") String personId){
        return personService.findById(personId);
    }
    
    @ResponseStatus(HttpStatus.OK)
	//Por padrão responde com o status code 200 success
    @RequestMapping(value = "/findAll",
		//Mapeia as requisições GET para localhost:8080/person/findAll
		method = RequestMethod.GET,
		produces = MediaType.APPLICATION_JSON_VALUE)
		// Produz JSON como retorno
    public List<Person> findAll(){
        return personService.findAll();
    }
    
    @ResponseStatus(HttpStatus.OK)
	//Por padrão responde com o status code 200 success
    @RequestMapping(method = RequestMethod.PUT,
	//Mapeia as requisições PUT para localhost:8080/person/
		consumes = MediaType.APPLICATION_JSON_VALUE,
		// Consome JSON enviado no corpo da requisição
		produces = MediaType.APPLICATION_JSON_VALUE)
		// Produz JSON como retorno
    public Person create(@RequestBody Person person){
        return personService.create(person);
    }
    
    @ResponseStatus(HttpStatus.OK)
	//Por padrão responde com o status code 200 success
    @RequestMapping(method = RequestMethod.POST,
	//Mapeia as requisições POST para localhost:8080/person/
		consumes = MediaType.APPLICATION_JSON_VALUE)
		// Consome JSON enviado no corpo da requisição
    public Person update(@RequestBody Person person){
        return personService.update(person);
    }

    @ResponseStatus(HttpStatus.OK)
	//Por padrão responde com o status code 200 success
    @RequestMapping(value = "/{personId}",
		method = RequestMethod.DELETE)
		//Mapeia as requisições DELETE para localhost:8080/person/ 
		//recebendo um ID como @PathVariable
    public void delete(@PathVariable(value = "personId") String personId){
        personService.delete(personId);
    }
}



Agora que terminamos nossa codificação acesse a classe Application e inicie a aplicação como no post anterior. Para testar nosso endpoint e cada uma de suas operações usaremos o plugin do Chrome Postman.


Primeiro faremos uma requisição do tipo GET chamando a operação findAll para o recurso localhost:8080/person/findAll. Como se pode ver na imagem abaixo obtemos como resposta um JSON com um array de pessoas.

postagem_2_2



Agora faremos uma requisição do tipo GET chamando a operação localhost:8080/person/1 passando como @PathVariable o ID da pessoa que desejamos recuperar. Da mesma forma que na requisição anterior recebemos como resposta um JSON com 1 objeto pessoa.

postagem_2_1

Agora faremos uma requisição do tipo POST (esse é o verbo REST usado para persistir informações) chamando a operação localhost:8080/person/ passando como parâmetro um JSON com um objeto pessoa no corpo da requisição. Para isso no Postman precisamos setar o tipo de requisição como POST, na aba Body definimos o JSON que representa o objeto pessoa a ser gravado, selecionamos a opção raw, definimos a opção JSON(application/json) e por fim executamos a request através do botão send.

postagem_2_3

Na imagem abaixo podemos ver o body da response retornada com um ID definido pela aplicação.


postagem_2_4

Agora faremos uma requisição do tipo PUT (esse é o verbo REST usado para atualizar informações) chamando a operação localhost:8080/person/ passando como parâmetro um JSON com um objeto pessoa no corpo da requisição. Lembrando que essa representação de pessoa deve ter um ID que será usado pra localizar e atualizar um recurso na base. Para isso no Postman precisamos setar informações similares ao post a unica diferença é que o tipo de requisição deve ser o PUT.



postagem_2_5

Como se pode ver na imagem abaixo o body da response retorna o nosso objeto JSON com as alterações.

postagem_2_6



Por fim faremos uma requisição do tipo DELETE (verbo REST usado excluir informações) chamando a operação localhost:8080/person/1 passando como @PathVariable o ID da pessoa que desejamos remover da base. Esse tipo de requisição retorna como resposta um body vazio e um StatusCode 200 caso a operação seja executada com sucesso os StatusCode 204 (no content), 401 (Unauthorized), 403 (forbiden), 404 (not found) ou 500 (internal server error) podem ser retornados.


postagem_2_7


Dessa forma abordamos os 4 verbos principais do HTTP usados em aplicações REST. Lembrando que você pode baixar o código aqui e descompactar o arquivo zip e importar não sua IDE de preferencia ou clonar usando Git:

git clone https://github.com/leandrocgsi/simple-rest-example-verbs.git



Continue ligado no blog, por que no próximo post iremos documentar a nossa API com o framewrok Swagger. E claro a abordagem nesses posts também será totalmente mão na massa. É isso aí bons estudos.

Tags: , , , ,

HTTP Status Codes em Serviços REST



Uma boa prática ao se desenvolver serviços REST é retornar status codes adequados a cada operação executada. Isso tem uma importância ainda maior quando se trata de uma exceção, uma vez que diferentementemente de um serviço SOAP uma aplicação verdadeiramente REST não retorna a exception para o usuário. Sendo assim é fortemente recomendado que ao se desenvolver uma API REST se vá além dos status code 200 e 500 OK e falha respectivamente.


200 OK – Request de criação ou deleção executada com sucesso.


201 Created – Criação de uma fila, topico, fila temporária, tópico temporária, session, producer, consumer, listener, queue browser ou menssagem realizada com sucesso.


204 No Content – deleção de uma fila, tópico, sessão, producer ou listener bem sucedida mas sem retorno de conteúdo.


400 Bad Request – O path informado está em umformato incorreto, um parametro ou valor do corpo da requisição não está formatado corretamente ou um parâmetro onbrigatório não foi informado,
ou está formatado corretamente mas pode estar eventualmente inválido (por exemplo, o ID informado não exite – NullPointerException, o conteúdo retornado é muito grande ou o ID informado já está em uso).


403 Forbidden – O cliente não tem permissão para executar requisições na operação em questão.


404 Not Found – o objeto requisitado pelo path não existe(NullPointerException).


405 Method Not Allowed – O usuário não tem permissão de acesso ao path.


409 Conflict – Um objeto já foi criado com as mesmas informações.


500 Internal Server Error – Ocorreu uma falha no servidor, podendo ser desde uma falha no SQL por exemplo.

A prática de retornar os status code corretos torna seus serviços mais amigáveis ao cliente facilitando a integração com outros sistemas. Nos próximos posts botaremos a mão na massa desenvolvendo a nossa propria API/Serviço REST.


Botando em prática


Em breve estarão disponíveis dois posts Criando o primeiro endpoint REST com SpringBoot e Documentando aplicações RESTfull com SpringBoot e Swagger com exemplos práticos.


Referências


DZone

Java World

StackOverflow

Oracle

Tags: , , , ,