Monkey e seus microserviços – POST 1

Já faz algum tempo que essa hype de microservices foi lançada e todo mundo começou a tentar entender e muitos a tentar usar, mas, como toda hype, os problemas começaram a aparecer bem rápido e existiram (e ainda existem), casos de empresas que estão voltando para o monolito devido a sua péssima experiência com microservices, e aí fica a pergunta: porque isso aconteceu?

Na minha humilde opinião, o que aconteceu foi afobamento, desconhecimento do assunto e, como tudo na vida, prazos apertados para entregas ilusórias. Migrar uma aplicação monolítica para microservices ou começar do zero é um processo um tanto quanto demorado e muito trabalhoso! Sem as ferramentas certas, o que você vai fazer é “dar um tiro no seu próprio pé”. E porque eu digo isso? Todos sem exceção já tiveram problemas ao “subir” uma aplicação para produção, e, se você não teve problema é porque nunca subiu nada em produção 🙂

Bom, também tivemos muitos casos de sucesso com microservices, mas porquê estes deram certo? Porque as migrações foram coordenadas e feitas devagar e com planejamento, ou então quem começou do zero primeiro “preparou a cama para se deitar”. E porque eu falo isso? A Monkey Exchange (minha atual empresa) foi um caso onde nasceu com microservices e não, nós não voltamos para o monolito \o/

E como isso aconteceu? Há mais ou menos um ano e meio eu decidi mudar de emprego e comecei a minha nova jornada na Monkey, eu já estava trabalhando como microservices há 3 anos, então já havia enfrentado vários percalços e passado por N problemas, então como já estava “calejado”, comecei de fato preparando tudo para que a tolerância a falha, o nível de logs, a visibilidade do que está no ar, o que caiu e o motivo por terem caído fosse a mais alta possível.

E para tudo isso escolhi a stack do Spring Cloud Netflix OSS .Primeiro porque já tinha familiaridade com o assunto, e segundo porque eu acredito que seja a stack mais completa para você trabalhar com microservices. E meu primeiro passo foi subir um Eureka Server e então comecei a criar o primeiro microservice(já com toda a preocupação com os logs e isso o Spring Cloud Sleuth me ajudou bastante), além disso criei um SSO com Spring Cloud Security e daí em diante a coisa foi seguindo, mas isso fica pra um outro dia aí passaremos por toda a stack da Monkey, hoje a ideia é falar de testes.

Poxa, de novo falar de teste? Esse assunto já tá mais batido que microservices sim, mas temos uma grande incógnita na cabeça de muitas pessoas e muita gente não gosta ou não quer fazer testes. A maioria tem a visão de que testar é chato, trabalhoso e em muito dos casos, perda de tempo. Quando eu discuto sobre esse assunto o ponto de parar pra mim é ouvir “testar é perda de tempo”. Na minha opinião isso é a coisa mais errada que alguém pode falar. Você leva seu carro em um mecânico e ele arruma o freio e depois você faz uma viagem de 200Km sem testar antes e ter certeza que funcionou? Se esse mecânico for um mecânico sério você acha que ele arrumou seu freio e não deu nenhuma volta com o seu carro? Ps: Meu pai é mecânico e ele sempre executa os testes 🙂

Na Monkey resolvemos então fazer três tipos de testes (abro um parênteses aqui para agradecer ao Marcin Grzejszczak (Spring Cloud Contract, Spring Cloud Sleuth e Spring Cloud Pipelines, além de autor dos livros Mockito Cookbook e Mockito Instant. “Se não me esqueci de nenhum outro rs”), que me ajudou muito nessa parte e me deu um direcionamento da melhor maneira de criar testes, inclusive chegou até a fazer um hangout comigo para discutirmos sobre o assunto.

Então escolhi por fazer o teste do contrato que garante que meus endpoints e minhas filas tenham contrato definido do input e output, testes unitários que são padrão na maioria dos projetos e o teste da camada de acesso a dados onde é usado o Spring Data. E como isso funciona?

O nosso processo de desenvolvimento segue algumas premissas:
1 – Tudo deve ser testado inclusive os repositories do Spring Data.
2 – O Sonar deve rodar a cada build da aplicação.
3 – A tarefa só está pronta depois de ir para produção.

Seguindo essas premissas o primeiro teste a ser feito deve ser o teste do repositório e ele deve se parecer com algo mais ou menos assim:

@DataJpaTest
@RunWith(SpringRunner.class)
public class SupplierRepositoryTest extends TestSupport {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private SupplierRepository repository;

    private Supplier supplier;

    @Override
    public void init() {
        Supplier supplier = Fixture.from(Supplier.class).gimme(VALID.name());
        entityManager.persist(supplier);
    }

    @Test
    public void testExample() {
        assertEquals(supplier, repository.findOne(
                SupplierSpecification.sponsorId(supplier.getSupplierId().getSponsorId())));
    }
}

Como é possível notar, só é testado o método novo que foi criado, e no nosso caso como usamos Specification (Tem um post falando sobre isso aqui no blog), então o que nós fazemos é testar se a Specification criada faz o que deveria fazer.

O primeiro passo então é gerar um registro em uma base H2 (Todos os testes rodam em base in memory) e depois validar se a Specification funciona. Nós testamos apenas o que criamos os demais métodos do repositório já é muito bem testado pela equipe que desenvolve o Spring Data.

Ps: Nós usamos o Fixture Factory para criação da massa de teste, é um framework muito legal e bem fácil de usar.

O próximo passo é o teste unitário de um serviço, esse tipo de teste acho que já é mais do que conhecido e minimamente um projeto deve ter no mínimo esse nível de teste. O teste unitário deve usar mock para as dependências e deve testar a sua regra de negócio. O teste deve parecer com o que está abaixo:

public class FeesServiceImplTest extends TestSupport {

    @Mock
    private FeesRepository feesRepository;

    private FeesService feesService;

    @Override
    public void init() {
        feesService = new FeesServiceImpl(feesRepository);
    }

    @Test
    public void findOne() throws Exception {
        Fees fees = Fixture.from(Fees.class).gimme(VALID.name());
        when(feesRepository.findOne(any(Specification.class))).thenReturn(fees);

        assertEquals(fees.getFee(), feesService.findOne(10, FeesTypeEnum.FLOAT_DEFAULT));

        InOrder inOrder = inOrder(feesRepository);
        inOrder.verify(feesRepository, times(1)).findOne(any(Specification.class));
        inOrder.verifyNoMoreInteractions();
    }
}

Nós fazemos testes unitários inclusive para os serviços da API, com isso nós garantimos que o serviço Rest faça o que ele deveria mesmo fazer. E porquê não testamos o Rest subindo o contexto? Porque isso é coberto pelo Spring Cloud Contract.

Por último fazemos o teste do contrato utilizando o Spring Cloud Contract, que faz a validação se o output e input do meu serviço são realmente o que eu esperava. Também o utilizamos para fazer os testes das nossas integrações via filas e HTTP. Abaixo um exemplo de um contrato:

package contracts.bankAccounts

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'POST'
        url $(consumer('v1/sellers/[0-9]{2}/bank-accounts'),
                producer("v1/sellers/10/bank-accounts"))
        headers {
            contentType(applicationJson())
        }
        body("""
            {
                "alias": "Conta",
                "bank": "033",
                "agency": "3333",
                "account": "66666666",
                "accountDigit": "6",
            }
        """)
    }
    response {
        status 201
        headers {
            contentType(applicationJson())
        }
        body("""
                {

                    "id": "${value(test(anyNumber()), stub(44))}",,
                    "alias": "${value(test(anyNonEmptyString()), stub("Conta"))}",
                    "bank": "${value(test(anyNonEmptyString()), stub("033"))}",
                    "agency": "${value(test(anyNonEmptyString()), stub("3333"))}",
                    "account": "${value(test(anyNonEmptyString()), stub("66666666"))}",
                    "accountDigit": "${value(test(anyNonEmptyString()), stub("6"))}",
                    "createdAt": "${value(test(anyNumber()), stub(1507146043725))}",
                    "createUserId": "${value(test(anyEmail()), stub("monkey_tester@monkey.com"))}",
                    "createUserIp": "${value(test(anyIpAddress()), stub("10.0.0.1"))}",
                    "updatedAt": "${value(test(anyNumber()), stub(1507146043725))}",
                    "lastUserId": "${value(test(anyEmail()), stub("monkey_tester@monkey.com"))}",
                    "lastUserIp": "${value(test(anyIpAddress()), stub("10.0.0.1"))}",
                    "links": {
                        "self": {
                            "href": "${value(test(regex('http://localhost/v1/sellers/[0-9]{2}/bank-accounts/[0-9]{2}')), stub("http://localhost/v1/sellers/10/bank-accounts/20"))}"
                        },
                        "seller": {
                            "href": "${value(test(regex('http://localhost/v1/sellers/[0-9]{2}')), stub("http://localhost/v1/sellers/10"))}"
                        }
                    }
                }
        """)
    }
}

Tem um post sobre contract aqui sobre esse assunto 🙂

Bom, agora que tudo foi testado, nós garantimos o funcionamento end to end de tudo, podemos abrir o Pull Request e essa nova funcionalidade vai subir para os ambientes.

E este foi o primeiro post de uma série falando sobre como nós criamos e gerenciamos os nossos microservices, se você tem algum comentário ou alguma dúvida não deixe de entrar em contato 🙂

Até a próxima.

Advertisements
Monkey e seus microserviços – POST 1

Spring Boot Scaffold CLI

spring-scaffold

Hoje vamos fazer um CRUD só com a CLI (command-line interface) do spring boot e um plugin que faz um scaffold.

O que é um scaffold?

Basicamente gerar para você o famoso CRUD (create, read, update, delete), usando linha de comando ou uma interface, IDE etc…

Nosso plugin está na primeira versão se você tiver interesse em contribuir o link do projeto é esse: https://github.com/NetoDevel/cli-spring-boot-scaffold

Veja demonstração em video

 

Você vai precisar

  • Maven
  • Spring CLI (1.4.1 ou superior)
  • Plugin spring-boot-scaffold 0.1

Instalação da Spring CLI

A maioria dos dev java não tem costume de usar cli para desenvolver, caso você não tenha instalado a cli do spring boot, confira abaixo se não pule esse passo.

Instalação Manual

A instalação manual consiste em você fazer o download da cli e setar como variável de ambiente em seu sistema operacional.

Link do zip:http://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.5.0.RELEASE/spring-boot-cli-1.5.0.RELEASE-bin.zip

Lembrando de setar na pasta /bin

Instalação usando SDKMan

Primeiro temos que instalar o SDKMAN e depois instalamos a cli por ele.(não funfa no windows)

Instalação SdkMan: http://sdkman.io/install.html

Após a instalação do sdkman

sdk install springboot 1.4.1.RELEASE
sdk use springboot 1.4.1.RELEASE

Pronto, agora que temos instalado a cli vamos instalar agora o pluginspring-boot-scaffold 0.1

Instalação Plugin spring-boot-scaffold

$ git clone https://github.com/NetoDevel/cli-spring-boot-scaffold.git
$ cd cli-spring-boot-scaffold
$ mvn install
$ spring install br.com.netodevel:spring-scaffold-cli:0.0.1-SNAPSHOT

Feito isso vamos iniciar a criação do nosso CRUD

Inicialmente vamos criar nosso projeto com as dependências necessárias.

spring init --dependencies=web,data-jpa,thymeleaf,mysql my-project

Próximo passo entrar no projeto e executar o comando de configuração

$ cd my-project
$ spring setup:scaffold

O comando spring setup:scaffold vai criar dois arquivos necessário para nosso scaffold, um é o scaffold.info onde vai conter informações para os nossos generates como nome do pacote, nome do banco e etc. Após isso ele vai gerar o application.properties com as informações default que foram salvas no scaffold.info.

Agora vamos rodar o comando scaffold

spring scaffold -n "User" -p "name:String email:String"

bem esse comando você já deve saber o que vai acontecer ele vai gerar o nosso crud 🙂

Após isso vamos executar mais dois comandos

$ spring db:create -p "mysql"
$ mvn spring-boot:run

O comando db:create -p “mysql” vai criar o nosso banco localmente, e o outro vai executar a aplicação.

Pronto somente com esses passos criamos um crud com spring boot.

Veja o resultado:


Obrigado pessoal até a próxima 🙂

Spring Boot Scaffold CLI

Spring Cloud Contract

Neste post vamos falar sobre Spring Cloud Contract, que é algo novo e que nem todo mundo conhece mas é muito útil para quem está trabalhando com microsserviços. A idéia principal é validar as suas integrações e garantir que o contrato não seja quebrado e que elas funcionem a cada novo deploy.

Vamos iniciar o projeto utilizando o Spring Initializer. Para começar, crie um projeto com as dependências do Cloud Contract Verifier e Web, como na imagem abaixo:

img1

Agora é preciso adicionar o plugin que gera os stubs para que você  possa utilizá-los nos seus testes integrados em outros microsserviços. O código que você tem que inserir está abaixo:

<plugin>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-contract-maven-plugin</artifactId>
   <version>1.1.0.RELEASE</version>
   <extensions>true</extensions>
   <configuration>
      <baseClassForTests>br.com.demo.MockMvcTest</baseClassForTests>
   </configuration>
</plugin>

É possível notar que o plugin possui uma classe base de teste, que é definida dentro da tag configuration do plugin. Essa é a sua classe base onde serão declarados os seus serviços rest que serão testados e terão stubs gerados. Abaixo um exemplo dessa classe:

package br.com.contract.demo;

import br.com.contract.demo.api.PaymentRestService;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;

public class MockMvcTest {

    @Before
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(new PaymentRestService());
    }
}

Legal, no exemplo a classe que está sendo testada é a PaymentRestService. Este é um exemplo que eu criei para este post, o que esse serviço faz é, executando um GET na url v1/payments/{id}/status caso o {id} seja maior que dez o retorno, será um pagamento não aprovado e, caso seja menor que dez, aí então o retorno será um pagamento aprovado. A classe em questão está no exemplo abaixo:

@RestController
@RequestMapping("v1/payments/{id}/status")
public class PaymentRestService {

    @GetMapping(produces = APPLICATION_JSON_VALUE)
    public ResponseEntity&lt;PaymentStatus&gt; getPayment(@PathVariable Integer id) {
        if (id &gt; 10) {
            return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED)
                    .body(PaymentStatus.builder().approved(false).build());
        } else {
            return ResponseEntity.ok(PaymentStatus.builder().approved(true).build());
        }
    }
}

Agora que nós já entendemos a classe que será testada, devemos criar um script groovy que será responsável pelo teste, o que ele irá fazer é enviar uma requisição e validar se o retorno está ok. O script para testar pagamento aprovado, deve ser igual ao abaixo:

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'GET'
        url '/v1/payments/1/status'
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body("""{"approved":true}""")
        headers {
            contentType(applicationJson())
        }
    }
}

E aqui o script para testar um pagamento não aprovado:

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'GET'
        url '/v1/payments/11/status'
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 412
        body("""{"approved":false}""")
        headers {
            contentType(applicationJson())
        }
    }
}

Estes scripts devem obrigatoriamente estar na pasta src/test/resources/contracts

Agora quando você executar o comando mvn clean package você verá que na pasta target foram criados alguns arquivos importantes:

  • generated-test-sources/contracts: Nesta pasta está a classe gerada pelo script groovy que, utilizando o RestAssured, valida o seu serviço rest.
  • stubs: Nesta pasta estão seus stubs, que podem ser publicados em um nexus para que outros projetos possam utilizá-los no momento dos testes integrados e, com isso, caso aconteça uma atualização o(s) projeto(s) que o utilizam irão ter seus testes quebrados e você não correrá o risco de subir uma alteração com uma integração não funcionando.

Para testar isso localmente execute o mvn clean install, assim os stubs vão para o seu maven local.

Esta foi a primeira parte, agora vamos criar um serviço que irá chamar a api de payment para validar se um pagamento está aprovado. Para isso, vamos criar um projeto no spring initializer com as seguintes dependências: Cloud Contract Stub Runner e Cloud Contract WireMock, igual ao da imagem abaixo:

img2Após o fazer o download do projeto, basta criar uma classe igual a classe abaixo para testar o contrato com o primeiro projeto criado. Esta classe irá fazer uma chamada ao stub, que irá retornar o contrato de acordo com os que foram criados com groovy.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(ids = {"br.com.demo:demo:+:stubs:8080"}, workOffline = true)
public class ValidateContractTest {

    private RestTemplate restTemplate = new RestTemplate();

    @Test
    public void validatePaymentApprovedContract() throws Exception {
        ResponseEntity&lt;String&gt; responseEntity = restTemplate.getForEntity(
                "http://127.0.0.1:8080/v1/payments/1/status", String.class);
        assertThat(responseEntity.getStatusCode(), equalTo(HttpStatus.OK));
        assertThat(responseEntity.getBody(), equalTo( "{\"approved\":true}"));
    }

    @Test
    public void validatePaymentNotApprovedContract() {
        try {
            restTemplate.getForEntity("http://127.0.0.1:8080/v1/payments/11/status", String.class);
        } catch (HttpClientErrorException ex) {
            assertThat(ex.getStatusCode(), equalTo(HttpStatus.PRECONDITION_FAILED));
            assertThat(ex.getResponseBodyAsString(), equalTo( "{\"approved\":false}"));

        }
    }
}

E é isso, agora todas as suas integrações podem ser mocadas e testadas de forma efetiva e acabou aquele problema de “quem removeu o atributo da api?”, para ver o código acesse o repo.

Abraços, não deixem de comentar as dúvidas e até o próximo post!

Spring Cloud Contract

Spring Boot um exemplo completo

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á!

1 – 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:

pic1

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.

2 – 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:

pic2

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.

3 – 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.

4 – 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.

Spring Boot um exemplo completo

Hystrix Circuit Breaker

Olá, no post anterior nós consumimos um serviço utilizando o Feig para fazer uma busca por um determinado CEP, mas nem tudo são flores e esse serviço pode estar fora do ar e a requisição não ser completada e aí teremos um problema, mas quase tudo tem solução e para esse problemas temos o Hystrix :).

O Hystrix implementa o padrão Circuit Breaker, que de forma bem rápida é um failover para chamadas entre micro serviços, ou seja, caso um micro serviço estiver fora do ar um método de fallback é chamado e aquela enxurrada de falhas é evitada.

Vamos usar o exemplo do post anterior (baixe o código aqui), bom o que vai ser feito é o seguinte, todas as requisições realizadas com sucesso vão ter os seus dados salvos em um MongoDB (poderia ser um Redis, ou deveria ser, mas o mongo já estava aqui na máquina rs) e caso não seja fechada a conexão com o Postmon será feita uma busca no MongoDB, se mesmo assim não for encontrada nenhuma informação para aquele CEP ai retornará um erro, afinal não se pode fazer milagre rs.

Agora é hora de colocar a mão no código, primeiramente é preciso adicionar as libs do Hystrix e do Spring Data MongoDB no projeto, basta adicionar isso no pom.xml.

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Dependências adicionadas, é hora de colocar as anotações do mongo na classe de response para que ela possa ser salva, veja como ela tem que ficar:

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@Document
public class CepResponse {

    @Id
    private String cep;
    private String logradouro;
    private String bairro;
    private String cidade;
    private String estado;
}

Agora essa classe já pode ser salva no mongo, o atributo “cep” é o Id e fica fácil fazer busca e o campo é automaticamente indexado.

Agora é hora de criar o Repositório para esse Documento do mongo, para isso é só criar uma interface igual a exibida abaixo:

import br.com.easy.cep.integration.CepResponse;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CepRepository extends PagingAndSortingRepository<CepResponse, String> {}

Após a interface criada é preciso salvar os dados quando a chamada for efetuada com sucesso, para isso é só alterar o serviço rest e deixá-lo assim:

import br.com.easy.cep.integration.CepResponse;
import br.com.easy.cep.integration.CepService;
import br.com.easy.cep.repository.CepRepository;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@AllArgsConstructor
@RequestMapping("v1/ceps")
public class CepRestService {

    private final CepService cepService;
    private final CepRepository cepRepository;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{cep}", method = RequestMethod.GET)
    public CepResponse getCep(@PathVariable String cep) {
        return cepRepository.save(cepService.getCep(cep));
    }
}

Feito isso os dados retornados vão ser salvos no mongo e podem ser utilizados caso algum problema aconteça com a chamada da API (Postmon), mas ainda assim pode ser que não tenhamos a informação buscada então vou criar uma exception “bonita” para que ela seja exibida no momento da falha da falha rs.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {}

Por último é só criar a classe que será chamada no caso de erro na integração e avisar o Feign que um fallback foi configurado então para isso é só criar uma nova classe que implementa a interface do Feig:

import br.com.easy.cep.exception.ResourceNotFoundException;
import br.com.easy.cep.repository.CepRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

import static java.util.Objects.isNull;

@Component
@AllArgsConstructor
public class CepServiceFallback implements CepService {

    private final CepRepository cepRepository;

    @Override
    public CepResponse getCep(String cep) {
        CepResponse cepResponse = cepRepository.findOne(cep);
        if(isNull(cepResponse)) {
            throw new ResourceNotFoundException();
        }
        return cepResponse;
    }
}

O que essa classe faz é buscar no mongo e caso não encontre ela devolve uma exception, agora para avisar o Feign do fallback basta adicionar o parâmetro fallback na anotação FeigClient:

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "cepService", url = "http://api.postmon.com.br", fallback = CepServiceFallback.class)
public interface CepService {

    @RequestMapping("/v1/cep/{cep}")
    CepResponse getCep(@PathVariable("cep") String cep);
}

A configuração do tempo de timeout é definida no application.propertie ou application.yaml como exemplificado abaixo:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 200

Para que sua aplicação habilite o Hystrix e seu Dashboard é preciso adicionar duas anotações na sua classe de startup:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@EnableHystrix
@EnableFeignClients
@SpringBootApplication
@EnableHystrixDashboard
public class EasyCepApplication {

   public static void main(String[] args) {
      SpringApplication.run(EasyCepApplication.class, args);
   }
}

E é isso, agora a aplicação tem uma tolerância maior a falha e você pode dormir um pouco mais tranquilo, espero que tenham gostado do post e qualquer dúvida é só comentar 🙂

Até o próximo post.

Hystrix Circuit Breaker

Consumindo serviços de forma fácil com spring-cloud-feign

Olá, muitas vezes precisamos consumir serviços web e sempre acabamos por escrever algumas linhas de código para que isso seja feito, hoje você verá que isso é coisa do passado e vamos integrar com um serviço de forma simples e criando apenas uma interface e uma classe que fará o parser do json.

Há alguns dias precisamos buscar um endereço através de um CEP e achamos uma lib bem bacana que faz isso, o Postmon, é uma lib feita em Python e então eu decidi pegar isso como exemplo e fazer uma API que consuma esse serviço e traga o endereço.

Bom, o Feign é um projeto que faz parte do grande guarda-chuvas de soluções do Spring Cloud e ele basicamente é utilizado para integração com serviços Rest, então vamos lá ver como isso funciona 🙂

feign

A API a ser chamada devolve um json com a seguinte estrutura:

{
  "bairro": "Bela Vista",
  "cidade": "São Paulo",
  "cep": "01317010",
  "logradouro": "Rua Professor Sebastião Soares de Faria",
  "estado_info": {
    "area_km2": "248.222,362",
    "codigo_ibge": "35",
    "nome": "São Paulo"
  },
  "cidade_info": {
    "area_km2": "1521,11",
    "codigo_ibge": "3550308"
  },
  "estado": "SP"
}

Legal, mas essa estrutura tem mais coisas do que eu quero então vamos dar uma “enxugada” nela e vamos devolver apenas os campos: logradouro, bairro, cidade e estado. Então criamos uma classe igual a exibida abaixo:

import lombok.Data;

@Data
public class CepResponse {
    private String logradouro;
    private String bairro;
    private String cidade;
    private String estado;
}

Beleza, após criar essa classe vamos criar a interface que FeignClient que irá fazer a chamada para o serviço, isso mesmo, criamos apenas uma interface e ela faz toda a “mágica” de chamar o serviço e fazer o parser dos dados. Veja como fica a interface:

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "cepService", url = "http://api.postmon.com.br")
public interface CepService {

    @RequestMapping("/v1/cep/{cep}")
    CepResponse getCep(@PathVariable("cep") String cep);
}

Agora é só criar o seu serviço rest que ficará exposto no seu projeto, para isso é só criar a classe abaixo:

import br.com.easy.cep.integration.CepResponse;
import br.com.easy.cep.integration.CepService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@AllArgsConstructor
@RequestMapping("v1/ceps")
public class CepRestService {

    private final CepService cepService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{cep}", method = RequestMethod.GET)
    public CepResponse getCep(@PathVariable String cep) {
        return cepService.getCep(cep);
    }
}

E por fim é só anotar a sua classe de startup com a anotação @EnableFeignClients:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients
@SpringBootApplication
public class EasyCepApplication {

   public static void main(String[] args) {
      SpringApplication.run(EasyCepApplication.class, args);
   }
}

Pronto, agora você já tem um endpoint que consome um outro endpoint de maneira simples e com poucas linhas de código, qualquer dúvida postem nos comentários, para ver o código é só acessar esse link.

Até o próximo post!

Consumindo serviços de forma fácil com spring-cloud-feign

Buscando por todos o campos com reflexão

Seguindo o post passado a idéia agora é evoluir as buscas do seu projeto, já vi em vários lugares apis que fazem busca por vários e vários atributos de uma classe, mas junto com isso vi vários e vários if’s encadeados verificando se tal ou tal atributo é nulo ou foi passado para a busca.

O que esse post quer mostrar é como você deixar sua ORM buscável com dois níveis de profundidade de objetos utilizando reflexão e fazendo isso de uma maneira simples de fácil manutenção e reutilização.

Bom, então chega de muito papo e vamos mostrar como fazer isso, dessa vez eu vou começar da API Rest para a base de dados, o exemplo do post anterior tem uma classe chamada PersonRestService e o conteúdo dela está assim:

@RestController
@RequestMapping("/persons")
public class PersonRestService {

    @Autowired
    private PersonService personService;

    @GetMapping
    public Page<Person> list(@RequestParam(required = false) String name,
                             @RequestParam(required = false) Integer cpf,
                             @RequestParam(required = false) Integer phone,
                             @RequestParam(defaultValue = "0") Integer page,
                             @RequestParam(defaultValue = "10") Integer size) {
        return personService.list(name, cpf, phone, new PageRequest(page, size));
    }

}

A princípio o código está correto e não existe problema algum, mas caso seja necessário adicionar mais um filtro e mais um e mais um a coisa fica feia, vai ser preciso adicionar mais parâmetros ao método, então vamos mudar para receber um Map<String, String>, assim a api será capaz de receber qualquer atributo de request e o código irá ficar assim:

@RestController
@RequestMapping("/persons")
public class PersonRestService {

    @Autowired
    private PersonService personService;

    @GetMapping
    public Page<Person> list(@RequestParam(required = false) Map<String, String> filters,
                             @RequestParam(defaultValue = "0") Integer page,
                             @RequestParam(defaultValue = "10") Integer size) {
        return personService.list(filters, new PageRequest(page, size));
    }

}

Agora que a API Rest já está preparada é hora de preparar o serviço para receber o Map de filtros, a classe de serviço PersonServiceProvider antes era assim:

@Service
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonRepository personRepository;

    @Override
    public Page<Person> list(String name, Integer cpf, Integer phone, Pageable pageable) {
        return personRepository.findAll(where(PersonSpecification.name(name))
                .or(PersonSpecification.cpf(cpf)).and(PersonSpecification.phone(phone)), pageable);
    }
}

Alterando o serviço ele deve ficar assim:

@Service
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonRepository personRepository;

    @Override
    public Page<Person> list(Map<String, String> filters, Pageable pageable) {
        return personRepository.findAll(filterWithOptions(filters), pageable);
    }
}

Agora vem a parte mais importante que é o Specification com o método que faz o filtro de todos os atributos por reflexão e caso ainda seja uma Lista é feito um Join, o código que faz a “mágica” ficou assim:

public class PersonSpecification {

    private static final String FIELD_SEPARATOR = ".";
    private static final String REGEX_FIELD_SPLITTER = "\\.";

    public static Specification<Person> filterWithOptions(final Map<String, String> params) {
        return (root, query, criteriaBuilder) -> {
            try {
                List<Predicate> predicates = new ArrayList<>();
                for (String field : params.keySet()) {
                    if (field.contains(FIELD_SEPARATOR)) {
                        filterInDepth(params, root, criteriaBuilder, predicates, field);
                    } else {
                        if (Person.class.getDeclaredField(field) != null) {
                            predicates.add(criteriaBuilder.equal(root.get(field), params.get(field)));
                        }
                    }
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            return null;
        };
    }

    private static void filterInDepth(Map<String, String> params, Root<Person> root, CriteriaBuilder criteriaBuilder,
                                      List<Predicate> predicates, String field) throws NoSuchFieldException {
        String[] compositeField = field.split(REGEX_FIELD_SPLITTER);
        if (compositeField.length == 2) {
            if(Collection.class.isAssignableFrom(Person.class.getDeclaredField(compositeField[0]).getType())) {
                Join<Object, Object> join = root.join(compositeField[0]);
                predicates.add(criteriaBuilder.equal(join.get(compositeField[1]), params.get(field)));
            }
        } else if(Person.class.getDeclaredField(compositeField[0]).getType().getDeclaredField(compositeField[1]) != null) {
            predicates.add(criteriaBuilder.equal(root.get(compositeField[0]).get(compositeField[1]), params.get(field)));
        }
    }

}

Pronto, agora você pode fazer a buscas do tipo /persons?name=Name, e além disso é possível realizar buscas como /persons?address.street mesmo que address seja uma lista, além disso o código pode ser evoluído com a criação de uma classe Abstrata e depois só é preciso estender essa classe e seu Specification terá essa capacidade.

Deixem dúvidas e comentários, valeu e até a próxima!

Buscando por todos o campos com reflexão