0

RESTful API com Spring Framework - Uma breve introdução.

#Spring Framework #Java
L
Lincon Silva

Resumo


Este artigo tem como objetivo descrever os passos de implementação e desenvolvimento de um projeto implementado em Java utilizando o ecossistema Spring.


1 Introdução


Logo abaixo será apresentado um breve contexto para o desenvolvimento da aplicação.


1.1 Contexto


Você está fazendo uma API REST que precisará gerar números aleatórios para loteria. Para facilitar na identificação da pessoa, você deverá associar cada número a um e-mail. No primeiro passo, precisamos construir um endpoint que receberá o e-mail da pessoa e retornará um objeto de resposta com os números sorteados para a aposta.Você deverá garantir que estas informações estejam gravadas em um banco de dados e devidamente associadas à pessoa. Também devemos construir um segundo endpoint paralistar todas as apostas de um solicitante, passando seu e-mail como parâmetro, o sistema deverá retornar em ordem de criação todas as suas apostas.


2 Setup Inicial


Definido o contexto da aplicação, iniciou primeiramente um projeto Java Spring através do próprio site do ecossistema chamado Spring Initializr (<https://start.spring.io/>). Muitas IDEs como o Eclipse já possuem integrada a elas a ferramenta para a inicialização de um projeto em Spring.


De inicio foram escolhidos os seguinte pacotes para começarmos a desenvolver a aplicação proposta, como pode ser observado na Figura 1. Logo abaixo, pode-se observar os pacotes escolhidos, uma breve descrição de cada pacote e o motivo de utiliza-lo no projeto. Lembrando que, a medida que for necessário incluir novos pacotes, nada impede de incluirmos ao decorrer do desenvolvimento da aplicação.


Figura 1 - Setup inicial - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/initial-setup.png


2.1 Pacotes instalados


•Spring Web - Pacote básico para aplicações web.

•Spring Data JPA - Spring Data JPA fornece suporte de repositóriopara a Java Persistence API (JPA). Facilita o desenvolvimento de aplicativos queprecisam acessar origens de dados JPA. JPA é uma coleção de classes e métodos voltados para armazenar persistentemente as vastas quantidades de dados em umbanco de dados. Com base no JPA vários FrameWorks são desenvolvidos (como oEclipseLink, Hibernet e TopLink) com o objetivo de proporcionar uma interação comum banco de dados relacional, evitando com que o desenvolvedor gaste tempo como desenvolvimento de códigos voltados para a manipulação dos dados presentes no banco de dados.

•Validation - Bean Validation é o padrão para implementar a lógica de validação no ecossistema Java. É bem integrado com Spring e Spring Boot.

•PostgreSL Driver - Driver que permite programa em Java se conectar com o banco de dados Postgres. - Este será o banco de dados utilizado para armazenar os dados no modo de produção. A escolha do banco de dados se deu pois este é o BD mais simples para realizar teste fazendo deploy no Heroku.

•Spring Boot DevTools - Provê um reinicio rápido da aplicação, live reload e mais algumas configurações para aprimorar a experiência de desenvolvimento.

•Lombok - uma biblioteca Java focada em produtividade e redução de código boiler plate que, por meio de anotações adicionadas ao nosso código, ensinamos o compilador (maven ou gradle) durante o processo de compilação a criar código Java.

•H2 Database - Um banco de dados mais simples, em memória que servirá para realização de testes.


2.2 Mais alguns pacotes


No decorrer da aplicação, foi necessário implementar mais alguns pacotes. Para manter a coerência na estrutura deste artigo, os pacotes serão descrito nesta sessão mas serão demonstrado sua motivação de uso nas sessões futuras.


•Spring Secutiry - Spring Security é uma estrutura Java / Java EEque fornece autenticação, autorização e outros recursos de segurança para aplicativos corporativos.

•Swagger - Swagger é um framework para descrição, consumo e visualização de serviços RESTful. E seu grande objetivo é permitir que a documentaçãopossa evoluir no mesmo ritmo da implementação, já que pode ser gerada automaticamente com base em anotações do código. A especificação OpenAPI, conhecida como OpenAPI Specification (OAS), é uma especificação para arquivos de interface legíveis por máquina para descrever, produzir, consumir e visualizar serviços de uma API RESTful.

•ModelMapper - O objetivo do ModelMapper é tornar o mapeamento de objetos fácil, determinando automaticamente como um modelo de objeto mapeia para outro, com base em convenções, da mesma forma que um humano faria - enquanto fornece uma API simples e segura para refatoração para lidar com casos de uso específicos.


       <dependency>
        <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.modelmapper.extensions</groupId>
            <artifactId>modelmapper-spring</artifactId>
            <version>2.3.0</version>
        </dependency>


Figura 2 - Pacotes adicionais - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/add-packages.png


3 Estrutura do Projeto


O projeto tem uma estrutura padrão muito utilizada para o desenvolvimento web. Dependendo do tamanho do projeto, pode existir mais ou menos pacotes que implementam novas funcionalidades. Na Figura 3 a seguir, pode-se observar a estrutura do projeto, mais abaixo é feita uma breve descrição de cada pacote e sua motivação; e dos arquivos mais importantes da aplicação.


•com.pacote.demo - Único pacote criado automaticamente ao iniciar uma aplicação com o Spring Boot. Por padrão ele vem nomeado de acordo com o preenchimento dos campos no momento da iniciação do projeto. Pode-se notar que o nome do pacote está "demo" pois eu não alterei a nomenclatura recomendada inicialmente pelo próprio Spring Initializr. Nesse pacote está o aplicativo de execução de toda a aplicação.

•com.example.demo.config - Pacote de configuração adicionais como por exemplo, o Security ou o Swagger. Aliás, estes foram os dois arquivos que foram configurados para utilizar nesta aplicação.

•com.example.demo.controllers - Pacote onde se localiza os controladores da aplicação. Esta é a primeira camada de acesso do usuário antes de passar para as próximas camadas da aplicação. Algumas validações acontecem nesta camada, e é nesta camada onde é criado os endpoints de uma aplicação REST API.

•com.example.demo.dto - DTO Data Transfer Object, está é mais uma camada de segurança da aplicação. Nos permite criar mais uma camada para que os dados passado pelo cliente não sejam passado através da mesma entidade que acessa o banco de dados. Além disso permite uma melhor modelagem na documentação da API. A utilização de DTOs motiva a utilização de mais uma nova biblioteca que será incluida mais a frente, a ModelMaper - esta biblioteca facilitará a conversão de objetosDTOs para objetos entidades de acesso ao banco de dados.

•com.example.demo.entities - Aqui é definidas as entidades de manipulação dos dados que, através do hibernate, será transformada em tabelas no banco de dados.

•com.example.demo.exceptionhandler - Embora o contexto da aplicação não requerer uma grande implementação de tratamento de erros, este pacote serve para cuidar de determinados erros devido tanto a inputs incorreto por parte da aplicação cliente quando para tratamento de erros do próprio servidor que por ventura possam ocorrer.

•com.example.demo.repositories - está é a camada mais próxima do banco de dados. Esta camada efetivamente faz as operações no banco de dados.

•com.example.demo.services - Os Services são responsáveis pela lógica de negócio da sua aplicação, além de ser responsável por se comunicar com as camadas mais internas do Software, como por exemplo, uma camada de Dados.

•resources - vale ainda citar três arquivos que definirão os profiles da nossa aplicação


1. application-test.properties - Perfil ativo no momento de teste da aplicação, utiliza-se neste perfil o banco de dados em memória H2.

2. application-dev.properties - perfil ativo no momento de desenvolvimento da aplicação, utiliza-se nesse perfil o mesmo banco de dados que será utilizado em produção. No nosso caso, o Postgres.

3. application-prod.properties - Configuração e propriedades no perfil de produção. Este perfil é o perfil que realmente está disponível para os consumidores da aplicação. Neste momento, a aplicação pode ser utilizada online.


Figura 3 - Estrutura do projeto - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/estrutura.png


4 Implementação


Nesta sessão será mostrada, na perspectiva de desenvolvimento, cada um dos diretórios demonstrados na sessão anterior. As sessões estarão sequenciadas de maneira diferente visando a lógica de criação que se teve no momento do desenvolvimento. Assim será possível criar a dúvida que é necessária para o aprendizado.


4.1 Entities


Levando em consideração o contexto abordado, a primeira tabela do banco de dados teria de guardar informações do usuário que joga na loteria. Como o único campo utilizado é o e-mail, não teremos uma classe tão complexa. Além do e-mail, necessitamos também do campo id pois este campo é necessário para o acesso e armazenamento das informações no banco de dados através das especificações do JPA implementadas pelo Hibernate. Por padrão, o nome da tabela no banco de dados é gerado de acordo com o nome da classe no Java, neste caso tivemos de mudar o nome da tabela através da notação @table uma vez que "user" é uma palavra reservada na liguagen SQL do postgres. Veja o código da Figura 4.


Figura 4 - User Entity - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/user-entity.png


@Getter
@Setter
@Builder
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User implements Serializable{
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Email @Column(unique = true)
    private String email;
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Lottery> bets;
}


Para completar o contexto, teremos que ter uma outra tabela que armazena os números sorteados. Teremos um relacionamento entre a tabela dos números sorteado com a tabela de usuarios. Nesse caso, optamos por fazer um relacionamento básico de muitos user para um email. Com esse relacionamento, por padrão, o id da tabela Users vai assim, ser uma coluna na tabela Lottery. Desse modo, um usuário poderá ter mais de um sorteio em seu nome. Veja a figura 5.


Figura 5 - Lottery Entity - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/lottery-entity.png


@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class Lottery implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String numbers;
    private Instant moment;
    @ManyToOne
    private User user;
}


4.2 Repositories


Tendo as entidades implementadas, teremos agora de fazer estas terem acesso aos comandos básicos do banco de dados através da camada Services. Porém, a camada Services necessita da camada Repositories para ter acesso aos métodos básicos disponibilizados pela JPA. Esta camada é uma das mais simples de implementar pois ela é apenas uma interface de acesso aos dados. Note que, apesar de esta implementação no disponibilizar diversos métodos de manipulação de dados em banco de dados, nada nos impede de criamos nossa própria query e executarmos. Outra possibilidade é criamos novos métodos apenas declarando-os na próxima interfaces como pode ser visto na linha 10 Figura 6 e na linha 13 da Figura 7.


Figura 6 - User Repository - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/user-repository.png


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.entities.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long>{
    public User findAllByOrderByEmail(String email);
}


Figura 7 - Loterry Repository - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/lottery-repository.png


@Repository
public interface LotteryRepository extends JpaRepository<Lottery, Long>{
    public List<Lottery> findByUser(User user);
}


4.3 Services


Uma vez implementada a camada Repository, podemos agora iniciar nossa implementação da camada de serviços, é nesta camada que será implementada as regras de negócios. Vale ressaltar que esta camada receberá sempre um DTO caso esta solicitação venha de um controller pois o DTO tem como objetivo ser mais uma camada de segurança que opera entre a camada controller e a camada de serviços. Como a camada de serviço sempre recebe um DTO, é dentro desta camada que utlizaremos o ModelMapper para converssão do DTO em Entity uma vez que é a entity que teremos de repassar ao banco de dados. Observe a Fifura 8 e a Figura 9. Note também a notação @Service que diz ao spring que esta é uma camada de serviço.


Figura 8 - User Service - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/user-service.png


@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    
    ModelMapper modelMapper = new ModelMapper();
    
    public User create(UserInputDto userInputDto) {        
        User user = modelMapper.map(userInputDto, User.class);
        
        return userRepository.save(user);
    }
    public User findByEmail(String email) {
        return userRepository.findByEmail(email);
    }
}


Figura 9 - Lottery Service - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/lottery-service.png


@Service
public class LotteryService {

    @Autowired
    private LotteryRepository lotteryRepository;

    ModelMapper modelMapper = new ModelMapper();

    public LotteryOutputDto bet(User user) {
        Random aleatorio = new Random();
        int valor = aleatorio.nextInt(999) + 1;
        
        Lottery lottery = Lottery.builder().moment(Instant.now()).user(user).numbers(Integer.toString(valor)).build();

        lottery = lotteryRepository.save(lottery);

        LotteryOutputDto lotteryOutputDto = modelMapper.map(lottery, LotteryOutputDto.class);

        return lotteryOutputDto;

    }

    public BetsByEmailDto findAllByUser(User user) {
        List<Lottery> lotteries = lotteryRepository.findByUser(user);

        if (lotteries.isEmpty()) {
            return BetsByEmailDto.builder().build();
        }

        UserInputDto userInputDto = UserInputDto.builder().email(user.getEmail()).build();

        List<LotteryOutputDto> lotteriesOutputDto = lotteries.stream()
                .map((lottery) -> modelMapper.map(lottery, LotteryOutputDto.class)).collect(Collectors.toList());
        
    

        BetsByEmailDto betsByEmailDto = BetsByEmailDto.builder().bets(lotteriesOutputDto.stream()
                .map(bet -> modelMapper.map(bet, BetsByEmailDto.Bet.class)).collect(Collectors.toList()))
                .user(userInputDto).build();

        return betsByEmailDto;
    }
}


4.4 DTOs


Já que a camada de serviços receberá DTOs - Objetos de Transferência de Dados - podemos então analisar os três DTOs que foram implementados. O DTO, além de ser uma camada de segurança, pode-se através dele, modelar os recebimento e a resposta às requisições. Note, por exemplo, que a classe BetsByEmailDTO - Figura 12 - poderia muito bem ser substituída pela própria entidade User - 4. Nesse sentido, a criação de BetsByEmailDTO foi criada também para entregar uma resposta melhor modelada, além de trazer mais segurança pois assim não estaremos enviando a própria entidade User como resposta. Neste mesmo sentido, criou-se os dois outros DTOs - Figura 10 - para entrada do e-mail do usuário no momento de fazer uma requisição de sorteio e Figura 11 - com o intuito de retornar os número sorteados.


Figura 10 - UserInputDTO - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/userinputdto.png


public class UserInputDto implements Serializable{
    private static final long serialVersionUID = 1L;
    
    @Email
    private String email;
}


Figura 11 - LotteryOutputDTO - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/lotteryoutputdto.png


public class LotteryOutputDto implements Serializable{
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String numbers;
    private Instant moment;
    private UserInputDto user;
}


Figura 12 - BetsByEmailDTO - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/betsbyemaildto.png


public class BetsByEmailDto implements Serializable{
private static final long serialVersionUID = 1L;
    
    private UserInputDto user;
    private List<Bet> bets;

    
    @Builder
    @Setter
    @AllArgsConstructor
    @Getter
    @NoArgsConstructor
    public static class Bet {
        private Long id;
        private String numbers;
        private Instant moment;
    }
}


4.5 Config


O diretório .confg contem algumas configurações essenciais para o funcionamento da aplicação e implementação de novos pacotes. Neste exemplo, na Figura 13, esta classe permitiu declarar algumas informações importantes. Dentre essas informações podemos ver a partir da linha 37 que foi implementado um método para liberar o Cors - Cross-origin resource sharing. Isto permite que a aplicação receba requisições de um outro host. Podemos ver também que no método 26 foi definido o csrf como desabilitado, essa API é stateless, ou seja, não guarda informações de sessões. Para completar, na linha 33 foi autorizado qualquer request. Isso foi definido pois, por padrão, o spring security solicita uma autenticação para acesso à API.


Figura 13 - Classe SecurityConfig - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/security-config.png


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private Environment env;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
            http.headers().frameOptions().disable();
        }
        
        http.cors().and().csrf().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeRequests().anyRequest().permitAll();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
        configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "OPTIONS"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}


Na Classe SwaggerConfig - Figura 14 - foi implementada uma configuração padrão para o funcionamento do Swagger. O Swagger permite o consumo e visualização de serviços RESTful. E seu grande objetivo é permitir que a documentação possa evoluir no mesmo ritmo da implementação, já que pode ser gerada automaticamente com base em anotações do código.


Figura 14 - Classe SwaggerConfig - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/swagger-config.png


@Configuration
@EnableSwagger2
public class SwaggerConfig {                                    
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}


4.6 Controllers


Esta é a camada de entrada de requests e saída de response. A camada mais próxima da aplicação cliente. Nesta camada definiremos as rotas e tipo de métodos para solicitar determinada informação da API ou salvar determinada informação através da API.


Esta classe deve ser anotada com a notação @RestController. Esta notação indica ao Spring que esta é a camada de controle da nossa aplicação. Pode-se também utilizar a notação @Controller, no entanto esta é uma notação mais generalista do que a @RestController.


Na linha 26 é definida o endpoint da nossa API. E o complemento dos endpoint pode ser visto nos dois métodos implementados dentro da classe UserController. Aliás estes dois métodos são exatamente os dois casos de uso requisitados na problematização. O método "generateNumbers", requer que seja repassado o e-mail do usuário, e partir deste e-mail, retornará o número sorteado para aquele e-mail. Já o segundo método "seeNumber" espera também receber um e-mail e retornará um JSON com o histórico com um conjunto de sorteios solicitados por aquele usuário. Ver Figura 15.


Figura 15 - Classe UserController - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/user-controller.png


@RestController
@RequestMapping(value = "/users")
@Validated
public class UserController {

    @Autowired
    private UserService userService;
    
    @Autowired
    private LotteryService lotteryService;
    
    @PostMapping(value = "/generate")
    public ResponseEntity<LotteryOutputDto> generateNumbers(@RequestBody @Valid UserInputDto userInputDto){
        
        User user = userService.findByEmail(userInputDto.getEmail());
        
        if(user == null) {
            user = userService.create(userInputDto);
        }
        
        LotteryOutputDto lotteryOutDto = lotteryService.bet(user);
        
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(user.getId()).toUri();
        
        return ResponseEntity.created(uri).body(lotteryOutDto);
    }
    
    @GetMapping("/numbers")
    public ResponseEntity<BetsByEmailDto> seeNumbers(@RequestParam("email") String email){
        User user = userService.findByEmail(email);
        
        if(user == null) {
            BetsByEmailDto betsByEmailDto = BetsByEmailDto.builder().user(UserInputDto.builder().email(email).build()).build();
            return ResponseEntity.ok(betsByEmailDto);
        }
        
        BetsByEmailDto betsByEmailDto = lotteryService.findAllByUser(user);
        
        return ResponseEntity.ok(betsByEmailDto);
    }
}


4.7 Exception Handler


Este diretório tem como principal objetivo tratar os diversos erros que podem ocorrer durante uma requisição. Trantando um erro, é possível enviar uma mensagem mais amigável para o usuário que requisitou algo assim como também enviar o código http correto do erro. Na nossa aplicação, o único erro tratado foi o formato do e-mail. Caso o cliente solicite um sorteio passando um e-mail mal formatado, a aplicação retornará um erro seguindo o padrão da classe problem, indicando qual foi o erro e o código http daquele erro - como pode-se ver na Figura 16. Note que o erro retornado tem o mesmo modelo da classe Problem - Figura 17.


Figura 16 - Retorno ao passar e-mail mal formatado - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/erro-badrequest.png


Figura 17 - Classe Problem - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/problem.png


@JsonInclude(Include.NON_NULL)
@Builder
@Getter
@Setter
public class Problem {
    private Integer status;
    private OffsetDateTime dataHora;
    private String titulo;
    private List<Campo> campos;

    @AllArgsConstructor
    @Getter
    @Setter
    public static class Campo {
        private String nome;
        private String mensagem;
    }
}


A classe {ApiExceptionHandler} é uma classe de tratamento de erro importante. Ela é definida com a notação {@ControllerAdvice} que mostra para o Spring que esta é uma classe de tratamento de error. Nesta classe pode definir como será tratado qualquer erro, tantos os já existente como qualquer outro erro que nós quisermos criar pois o desenvolvedor tem a possibilidade de criar novos tipos de erros além dos existentes.


Em nosso caso, tratamos apenas um tipo de erro atraves de um {@Override} de um método já existente. É justamente esse método que trata o e-mail mal formatado. Além disso, foi implementado para mostrar mais erros casos existissem mais campos. Ver Figura 18.


Figura 18 - Classe ApiExceptinoHandler - https://github.com/zlincon/OrangeTalentsChallenge/blob/main/img/apiexceptionhandler.png


@ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
    @Autowired
    private MessageSource messageSource;
    

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
            HttpHeaders headers, HttpStatus status, WebRequest request) {

        var campos = new ArrayList<Problem.Campo>();

        for (ObjectError error : ex.getBindingResult().getAllErrors()) {
            String nome = ((FieldError) error).getField();
            String mensagem = messageSource.getMessage(error, LocaleContextHolder.getLocale());

            campos.add(new Problem.Campo(nome, mensagem));
        }

        var problema = Problem.builder().status(status.value())
                .titulo("Um ou mais campos estão inválidos. Preencha corretamente e tente novamente.")
                .dataHora(OffsetDateTime.now()).campos(campos).build();

        return super.handleExceptionInternal(ex, problema, headers, status, request);
    }
}


5 Conclusão


Neste artigo foi mostrado um modelo de estrutura básica de um projeto Spring baseado em um determinado contexto disponibilizado nas sessões inciais. No que se refere ao item bônus, quando se fala em e-mail duplicado, a aplicação desenvolvida já impede a criação de e-mails duplicados porque, antes de sortear determinado número para um usuário, na camada controller já é feito uma verificação para saber se existe um usuário com aquele determinado e-mail cadastrado, caso não exista, salva o novo e-mail; caso exista, somente sorteia um novo número - ver Figura 15.


Já levando em consideração ao requerimento gerar sorteio de número único para cada usuário, levando em consideração uma aplicação real, nada impede que duas pessoas joguem o mesmo número em um sorteio, logo não foi implementado. Ainda sim, nada impede que isto seja implementado futuramente.


5.1 Deploy


Esclareço que esta aplicação está online e foi feita o deploy através do Heroku, e pode ser acessada através do link {https://orangetallents-challenge.herokuapp.com}. A documentação da API foi gerada pelo Swagger e pode ser encontrada na uri {https://orangetallents-challenge.herokuapp.com/swagger-ui.html} - recomenda-se testar a API através do {Postman} ou do {Insomnia}. O Código está disponibilizado no Github pelo link {https://github.com/zlincon/OrangeTalentsChallenge/tree/v1}.


Há ainda a possibilidade de implementar um pequeno formulário web para acessar a API e realizar as requisições proposta no contexto.


Acrescento que modificações poderão acontecer após a entrega deste artigo, deste modo, para acessar o estado da aplicação tal qual estava no momento da entrega deste artigo, favor acessar a branch {v1}, uma vez que a master pode ocorrer breve atualizações para aprimorar cada vez mais este desafio proposto.


No mais, espero que este artigo tenha sido esclarecedor àqueles que o lerem. :)

0
16

Comentários (0)

None

Brasil