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:
- Como vou criar o projeto?
- Como vou organizar os pacotes?
- Onde ficam as regras de negócio?
E, se for uma API RestFul, a coisa fica ainda pior e outras perguntas surgem:
- Como faço o versionamento da API?
- Vou devolver a minha entidade toda na API?
- 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:
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:
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.