Trilha Java EE
Emmanuel Neri
CONSTRUINDO APIS DE FORMA PRODUTIVA COM SPRING BOOT, SPRING DATA E SPRING MVC
EMMANUEL NERI
‣ Mestre em Desenvolvimento de Tecnologia
‣ Desenvolvedor desde 2010
‣ Atualmente desenvolvedor back-end na Navita
)(
PROBLEMA
Quais os obstáculos da construção de APIs?
Ambiente Objeto <—> JSON Acessos aos dados
AGENDA
Spring Boot
Spring MVC Spring Data
SPRING BOOT
Spring Boot
AUTO CONFIGURAÇÃO
EMBEDDED SERVLET
CONTAINERS
GERENCIAMENTO DE DEPENDÊNCIAS
GERENCIAMENTO DE DEPENDÊNCIAS
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>1.5.3.RELEASE</version> </dependency>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.3.RELEASE</version> </plugin> </plugins> </build>
GERENCIAMENTO DE DEPENDÊNCIAS
AUTO CONFIGURAÇÃO
@SpringBootApplication public class AppConfig { public static void main(String[] args) { SpringApplication.run(AppConfig.class, args); } }
@EnableAutoConfiguration
@AutoConfigureTestDatabase
@AutoConfigureMockMvc
AUTO CONFIGURAÇÃO
========================= AUTO-CONFIGURATION REPORT =========================
Positive matches: ----------------- EmbeddedServletContainerAutoConfiguration matched: ... DataSourceConfiguration.Tomcat matched: ... Negative matches: ----------------- ActiveMQAutoConfiguration: Did not match:
RedisAutoConfiguration: Did not match:
mvn spring-boot:run -Ddebug
AUTO CONFIGURAÇÃO
/src/main/resources/application.properties
spring.profiles.active=dev
server.port=8090 server.context-path=/api
spring.http.encoding.charset=UTF-8
spring.datasource.url=jdbc:postgresql://localhost:5432/db spring.datasource.username=postgres spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.show-sql=true
AUTO CONFIGURAÇÃOspring.profiles.active=
spring.mail.host=
spring.sendgrid.api-key=
flyway.enabled=true # Enable flyway
liquibase.enabled=true
spring.data.cassandra.cluster-name=
spring.data.elasticsearch.cluster-name=
spring.data.mongodb.database=test
spring.data.redis.repositories.enabled=true
spring.activemq.broker-url=
…. https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
EMBEDDED SERVLET CONTAINERS
mvn spring-boot:run
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
SPRING MVC
Spring MVC
FACILIDADE NA LEITURA DE
PARÂMETROS
ABSTRAÇÃO NA SERIALIZAÇÃO / DESERIALIZAÇÃO
SIMPLICIDADE NA EXPOSIÇÃO DE APIS
CONSTRUÇÃO SIMPLES
ESTRUTURA DE RETORNO
TRATAMENTO DE ERROS
SIMPLICIDADE NA EXPOSIÇÃO DE APIS
@RestController @RequestMapping("/customers") public class CustomerController {
@RequestMapping(method = RequestMethod.GET) public List<Customer> findAll() { return customerService.findAll(); } }
http://localhost:8080/customers
ABSTRAÇÃO NA SERIALIZAÇÃO / DESERIALIZAÇÃO
@RequestMapping(method = RequestMethod.GET) public List<Customer> findAll() {
return customerService.findAll(); }
@RequestMapping(method = RequestMethod.POST) public void save(@RequestBody BillDTO billDTO) { billService.save(billDTO); }
ABSTRAÇÃO NA SERIALIZAÇÃO / DESERIALIZAÇÃO
import com.fasterxml.jackson.annotation.JsonFormat;
public final class BillDTO { @JsonFormat(pattern = "yyyy-MM") private YearMonth yearMonth;
FACILIDADE NA LEITURA DE PARÂMETROS
@RequestMapping(value = "/byUk/{customerId}/{identifier}/{yearMonth}", method = RequestMethod.GET)
public Bill findByUk(@PathVariable("customerId") Long customerId, @PathVariable("identifier") String identifier, @PathVariable("yearMonth") YearMonth yearMonth) { return billService.findByUk(customerId, identifier, yearMonth); }
@RequestMapping(value = "/search", method = RequestMethod.GET) public List<Customer> search(
@RequestParam(value = "id", required = false) Long id, @RequestParam(value = "name", required = false) String name){
return customerService.search(new CustomerSearchTO(id, name)); }
CONSTRUÇÃO SIMPLES ESTRUTURA DE RETORNO
ResponseEntity.ok(bill);
ResponseEntity.badRequest().build();
@RequestMapping(method = RequestMethod.GET) public ResponseEntity ok() {
return ResponseEntity.ok().build(); }
200
400
200
{ "id":1, "customer":{ "name":"Customer" }, "carrier":{ "name":"TIM" }, "identifier":"23326", “yearMonth”:"2017/06" }
ResponseEntity.status(UNAUTHORIZED).build(); 401
TRATAMENTO DE ERROS
@ControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) public ResponseEntity<Set<ExceptionTO>> handleError(BusinessException ex) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Collections.singleton(new ExceptionTO(ex.getMessage()))); }
@ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<Set<ExceptionTO>> handleError(ConstraintViolationException ex) { final Set<ExceptionTO> erros = ex.getConstraintViolations().stream() .map(c -> new ExceptionTO(c.getMessage())) .collect(Collectors.toSet()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(erros); }
@ExceptionHandler(Exception.class) public ResponseEntity handleError(Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("O sistema se encontra indisponível”); }
SPRING DATA
Spring Data
SUPORTE A DIFERENTE FONTES DE
DADOS
ABSTRAÇÃO NO ACESSO AOS
DADOS
IMPLEMENTAÇÕES PADRÕES
CACHE
IMPLEMENTAÇÕES PADRÕES
public interface BillRepository extends JpaRepository<Bill, Long> {
‣ findAll()
‣ save(T object)
‣ save(Iterable<T> objects)
‣ flush()
‣ getOne(ID id)
‣ delete(ID id)
‣ deleteInBatch(Iterable<T> objects)
ABSTRAÇÃO NO ACESSO AOS DADOS
public Customer save(Customer customer) { return customerRepository.save(customer); }
public List<Customer> findAll() { return customerRepository.findAll(); }
public Page<Customer> findPaginable(int page, int size) { final Sort sort = new Sort(Sort.Direction.DESC, "name"); return customerRepository.findAll(
new PageRequest(page, size, sort)); }
ABSTRAÇÃO NO ACESSO AOS DADOS
Bill findByCustomerIdAndIdentifierAndYearMonth(Long customerId, String identifier, YearMonth yearMonth);
@Modifying @Query("delete from BillItem where bill.id = :#{#billId}") void deleteByBillItem(@Param("billId") Long billId);
Hibernate: select bill… from bill bill0_ where bill0_.customer_id=? and bill0_.identifier=? and bill0_.year_month=?
Hibernate: delete from bill where id=?
ABSTRAÇÃO NO ACESSO AOS DADOS
‣ Integração com QueryDSL
public interface BillRepository extends JpaRepository<Bill, Long>, QueryDslPredicateExecutor<Bill> {
public Page<Bill> search(BillSearchTO searchTO) { return billRepository.findAll(searchTO.toPredicate(), new PageRequest(searchTO.getPage(), searchTO.getSize(),
new Sort(Sort.Direction.DESC, "id"))); }
public class BillSearchTO {
public BooleanBuilder toPredicate() { final QBill qBill = QBill.bill; final BooleanBuilder predicate = new BooleanBuilder();
if(!Strings.isNullOrEmpty(identifier)) { predicate.and(qBill.identifier.eq(identifier)); }
if(initYearMonth != null) { predicate.and(qBill.yearMonth.goe(initYearMonth)); } ....
ACESSO AOS DADOS
‣ Repositorios customizadospublic interface BillRepositoryCustom {
Page<BillDTO> findAll(BillSearchTO searchTO); }
public interface BillRepository extends JpaRepository<Bill, Long>, QueryDslPredicateExecutor<Bill>, BillRepositoryCustom { ...
public class BillRepositoryImpl implements BillRepositoryCustom {
@PersistenceContext private EntityManager entityManager;
@Override public Page<BillDTO> findAll(BillSearchTO searchTO) { final JPQLQuery jpaQuery = new JPAQuery(entityManager) .from(qBill) .join(qBill.carrier).fetchJoin() .join(qBill.customer).fetchJoin() .join(qBill.items).fetchJoin(); ...
SUPORTE A DIFERENTE FONTES DE DADOS
‣ Módulos da Spring
‣ Módulos da comunidade
JPA LDAP
REST
SUPORTE A DIFERENTE FONTES DE DADOS
public interface BillFileRepository extends MongoRepository<BillFile, String> { }
import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "bill_file") @Getter public class BillFile {
@Id private String id; private String date; private String content
CACHE
@SpringBootApplication @EnableCaching public class AppConfig {
@Cacheable("carriers") public List<Carrier> findAll() { return carrierRepository.findAll(); }
‣ JSR-107
‣ Caffeine
‣ Guava cache
‣ EhCache
‣ GemFire
FIM …
OBRIGADO! https://github.com/emmanuelneri/productivity-with-spring
https://github.com/emmanuelneri
@emmanuelnerii
www.emmanuelneri.com.br