Desenvolvimento

1 ago, 2017

Spring Boot um exemplo completo

Publicidade

Olá, hoje quero abordar um assunto que acho que vai tirar as dúvidas de muitas pessoas, pois sempre que criamos um projeto surgem algumas perguntas:

  1. Como vou criar o projeto?
  2. Como vou organizar os pacotes?
  3. Onde ficam as regras de negócio?

E, se for uma API RestFul, a coisa fica ainda pior e outras perguntas surgem:

  1. Como faço o versionamento da API?
  2. Vou devolver a minha entidade toda na API?
  3. Onde eu coloco os links dos meus resource?

O que eu quero é criar um projeto e sanar todas essas dúvidas que assolam os desenvolvedores e também já me tiraram noites de sono. A idéia aqui é realmente se preocupar com todas as regras de qualidade e separação do código e usar frameworks que nos ajudem a fazer isso, então vamos lá!

Como vou criar o meu projeto?

Bom, essa é uma dúvida muito comum, eu hoje sempre acredito que você ganhará mais velocidade e produtividade se usar o spring-boot, não estou nem falando sobre usar toda a stack do spring-cloud, que não é o foco aqui, mas criar um projeto spring-boot com spring-data e pronto. Desta forma, você irá reduzir drasticamente a quantidade de código que terá que escrever. Então crie um projeto utilizando o https://start.spring.io/ como na imagem abaixo:

https://start.spring.io/
https://start.spring.io/

As dependências utilizadas são: Web, JPA, H2 (Para eu não configurar um banco de dados), Hateoas e Lombok. Pra quem não conhece o Lombok, ele possui algumas anotações que evitam que tenhamos que escrever getters, setter, construtores, equals, hashcode, builders e etc.

Como vou organizar os pacotes?

Agora que o projeto já está criado, é hora de começar a organizar as coisas. Já vi pessoas que gostam de dividir o projeto em módulos, e isso normalmente deixa o projeto com o módulo de domain (ORM e acesso a dados), core (Classes de negócio) e um módulo onde ficam os serviços web. A primeira vista isso parece legal, mas acaba onerando muito tendo que criar um pom pai e orquestrar todas as dependências do maven e todos os plugins e etc.

Prefiro fazer apenas um projeto e separar tudo isso em pacotes, assim o meu projeto fica como exibido na imagem abaixo:

Spring Boot

E os pacotes ficaram assim:

  • API: Todos os serviços RestFul, resources e assemblers.
  • Configuration: Todas as classes de configuração do spring, caso sejam necessárias.
  • Domain: Todo o acesso a dados fica aqui, ORM’s, repositories e specifications.
  • Service: Todos os serviços onde estão as regras de negócio, validações e o que mais for preciso.

A classe de start do spring eu deixo fora de qualquer pacote, porque ela sempre é usada e não é legal ter que ficar procurado por ela o tempo todo.

Onde ficam as regras de negócio?

As regras de negócio são o core do business e elas devem ser fáceis de alterar, fáceis de encontrar e fáceis de entender. Vejo pessoas que divagam na abstração e fazem coisas que nem mesmo elas, depois de dois dias, são capazes de entender, então faça um código que resolva o problema, e, caso ele precise ser mais abstrato ou mais extensível, aí então você se preocupa com isso. Lembre-se que entregar funcionalidade ao usuário é o que mais importa, você é um desenvolvedor e quer que as pessoas usem coisas que você criou.

Os serviços são @Service para o Spring e devem ter uma interface. Eu normalmente coloco o nome da interface como BookService e o nome da implementação como BookServiceProvider. O serviço deve receber dados da api, passar pelas suas regras e armazenar ou consultar na base de dados.

Os serviços acessam os repositories, ORM’s e Specifications que estão no pacote domain, então vamos criar uma ORM e um repository, para isso crie um pacote orm e um pacote repository dentro do pacote domain e então crie as classes abaixo:

Book orm:

package br.com.sample.domain.orm;
 
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
 
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book {
 
    @Id
    @GeneratedValue
    private Integer id;
    private String title;
    private String author;
    private Integer pages;
}

Book Repository:

package br.com.sample.domain.repository;
 
import br.com.sample.domain.orm.Book;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface BookRepository extends CrudRepository<Book, Integer> {}

Perfeito, feito isso é hora de criar a interface e a implementação do serviço e elas devem ficar como o exemplo abaixo:

A interface:

package br.com.sample.service;
 
import br.com.sample.domain.orm.Book;
 
public interface BookService {
 
 Book getById(Integer id);
 Book save(Book book);
}

A implementação:

package br.com.sample.service;
 
import br.com.sample.domain.orm.Book;
import br.com.sample.domain.repository.BookRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
 
import static java.util.Objects.isNull;
 
@Service
@AllArgsConstructor
public class BookServiceImpl implements BookService {
 
 private final BookRepository bookRepository;
 
 @Override
 public Book getById(Integer id) {
 Book book = bookRepository.findOne(id);
 if(isNull(book)) {
 throw new RuntimeException("Book not found!");
 }
 return book;
 }
 
 @Override
 public Book save(Book book) {
 //QUALQUER REGRA DE NEGÓCIO OU VALIDAÇÃO ANTES DE SALVAR
 return bookRepository.save(book);
 }
}

Lembre-se todas as regras de negócio devem ficar no serviço e devem ser legíveis, bem escritas e bem testadas.

Como faço o versionamento da API?

Eu particularmente prefiro fazer o versionamento via URL, fica claro e fácil de saber qual a versão do resource você está acessando então as urls ficam v1/books, v2/books e etc. E no projeto isso fica versionado em um pacote, então crie um pacote v1 dentro do seu pacote api e pronto sua API já tem versionamento. Feito isso é hora de criar o endpoint RestFul, para isso crie uma classe dentro do pacote api.v1 chamada BookResource e ela quem vai ser o retorno da API, justamente para não devolvermos a ORM, a classe deve ficar como a do exemplo abaixo:

package br.com.sample.api.v1;
import lombok.Data;
 import lombok.EqualsAndHashCode;
 import org.springframework.hateoas.ResourceSupport;
 import org.springframework.hateoas.core.Relation;
@Data
 @EqualsAndHashCode(callSuper = true)
 @Relation(value="book", collectionRelation="books")
 class BookResource extends ResourceSupport {
private String title;
 private String author;
 private Integer pages;
 }

Pode parecer estranho criar uma classe igual a ORM para ser devolvida via API, é estranho sim nesse nosso caso, mas e se você colocar mais informações na ORM e não quer que elas sejam expostas como você vai fazer? Dessa maneira o seu problema já foi solucionado antes de acontecer.

Criado o resource é hora de criar o endpoint serviço rest que vai receber as chamadas, para isso criar uma class chamada BooRestService como a do exemplo abaixo:

package br.com.sample.api.v1;
 
import br.com.sample.service.BookService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
 
@RestController
@AllArgsConstructor
@RequestMapping("v1/books")
public class BookRestService {
 
    private final BookService bookService;
 
    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public BookResource getById(@PathVariable Integer id) {
        throw new NotImplementedException();
    }
 
    @ResponseStatus(HttpStatus.CREATED)
    @RequestMapping(method = RequestMethod.POST)
    public BookResource save(@RequestBody BookResource bookResource) {
        throw new NotImplementedException();
    }
 
}

Bom, você deve ter notado que a classe ainda não tem implementação dos métodos e isso acontece porque eu preciso criar o assembler que ira resolver os próximos dois problemas que são “Onde eu coloco os links dos meus resource?” e “Vou devolver a minha entidade toda na API?”.

O AssemblerResource é onde são adicionados os links e a conversão de Domain -> Resource e Resource -> Domain é realizada. O resource deve ser como o exemplo abaixo:

package br.com.sample.api.v1;
 
import br.com.sample.domain.orm.Book;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import org.springframework.stereotype.Component;
 
@Component
public class BookResourceAssembler extends ResourceAssemblerSupport<Book, BookResource> {
 
 public BookResourceAssembler() {
 super(BookRestService.class, BookResource.class);
 }
 
 @Override
 public BookResource toResource(Book book) {
 BookResource bookResource = createResourceWithId(book.getId(), book);
 bookResource.setAuthor(book.getAuthor());
 bookResource.setPages(book.getPages());
 bookResource.setTitle(book.getTitle());
 addLinks(bookResource);
 return bookResource;
 }
 
 public Book toDomain(BookResource bookResource) {
 return Book.builder()
 .author(bookResource.getAuthor())
 .pages(bookResource.getPages())
 .title(bookResource.getTitle())
 .build();
 }
 
 private void addLinks(BookResource bookResource) {
 //Links de exemplo você pode usar o linkTo(methodOn())
 bookResource.add(new Link("http://localhost:8080/v1/foo", "foo"));
 bookResource.add(new Link("http://localhost:8080/v1/bar", "bar"));
 }
}

O assembler cria automaticamente o link self poupando o seu trabalho. Agora é possível terminar a implementação do rest service e deve ficar como o exemplo:

package br.com.sample.api.v1;
 
import br.com.sample.service.BookService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
 
@RestController
@AllArgsConstructor
@RequestMapping("v1/books")
public class BookRestService {
 
 private final BookService bookService;
 private final BookResourceAssembler bookResourceAssembler;
 
 @ResponseStatus(HttpStatus.OK)
 @RequestMapping(value = "/{id}", method = RequestMethod.GET)
 public BookResource getById(@PathVariable Integer id) {
 return bookResourceAssembler.toResource(bookService.getById(id));
 }
 
 @ResponseStatus(HttpStatus.CREATED)
 @RequestMapping(method = RequestMethod.POST)
 public BookResource save(@RequestBody BookResource bookResource) {
 return bookResourceAssembler.toResource(bookService.save(bookResourceAssembler.toDomain(bookResource)));
 }
 
}

Pronto, agora temos um projeto bem organizado e de fácil manutenção. Para ver o projeto que foi criado é só acessar o link.

Comentem as dúvidas, espero que tenham gostado e até a próxima.