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.




