Skip to content
/ crud Public

Uma abordagem "It's Time To Kick *** And Chew Bubblegum"

Notifications You must be signed in to change notification settings

Caiuzu/crud

Repository files navigation

Caio Souza Repository size Repository size Bugs Bugs Code Smells Lines of Code Coverage Java CI with Gradle License

ResumoSpring InitializrDockerPostgreSQLORMLombokTestesComo Contribuir?


Resumo:

Este projeto tem por intuito introduzir o desenvolvedor a algumas tecnologias e ferramentas complementares, vastamente utilizadas no dia-a-dia. Através de um CRUD simples, implementaremos os itens descritos abaixo.

Tecnologias/Ferramentas Utilizadas no Projeto:

Tecnologias utilizadas e explicadas previamente no projeto hello-world:
Novas Tecnologias utilizadas neste projeto:
  • JUnit 5 (Complementando)

    • Considerar a utilização do Teste Unitário para tudo, menos entidades e controller( nos controllers utilizar teste de integração com cucumber)
  • Docker

    • Docker Composer
    • Portainer.io
    • PostgreSQL
  • ORM

    • JPA
      • Audited JPA Envers
      • Hibernate Envers
    • Hibernate
  • Testes

    • Mockito
    • Factory
  • Core

    • Lombok
    • Pagination
    • VO
    • Validator
    • Tratamento de Exceptions
    • Builder
  • AWS

  • New Relic


0. Spring Initializr:

  • O que é:

    • Spring Initializr fornece uma interface web bem simples para o usuário. Podendo gerar seu projeto a partir de uma estrutura de configurações pré-moldadas. São configurações de versões do java/spring boot, grupo/nome do projeto, série de lista de dependências, etc.
  • Inicializando projeto com Spring initializr:

    Inicializando nosso projeto com Spring initializr:
    • Inicializaremos o projeto através do spring initializr utilizando os parâmetros abaixo:

      • Project: Gradle Project;
        • Language: Java;
        • Spring Boot: 2.6.5;
        • Project Metadata:
          • Group: br.com.simple
          • Artifact: crud
          • Name: crud
          • Description: Simple CRUD API
          • Package name: br.com.simple.crud
          • Packaging: jar
          • Java: 11
        • Dependencies: Spring Web, Spring Data JPA, PostgreSQL Driver, Lombok
  • Spring Quickstart:

    Spring Quickstart


1. Docker:

Configurando Docker e Docker-Compose:

Antes de seguir os passos abaixo, garanta que seu docker está instalado conforme explicado no inicio deste documento.

  • O que é:

    • Docker, é basicamente um container. Ele usa os próprios recursos do kernel de nosso SO para "simular" uma nova máquina. Diferente de como faz uma VM (que gera um novo SO para realizar esta tarefa).
    • O docker-compose faz a orquestração desses containers. Assim, possibilitando uma infra local rápida e eficiente.
  • Preparando ambiente Docker:

    Entendendo e Configurando Docker Composer no projeto:

    1.1. Preparando ambiente Docker:

    • Source: Tutorial DigitalOcean

      1.1.1 — Instalando Docker

      • Primeiro, atualize sua lista existente de pacotes:

        sudo apt update
      • Em seguida, instale alguns pacotes de pré-requisitos que permitem ao apt usar pacotes sobre HTTPS:

        sudo apt install apt-transport-https ca-certificates curl software-properties-common
      • Em seguida, adicione a chave GPG para o repositório oficial do Docker ao seu sistema:

        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
      • Adicione o repositório Docker às fontes APT:

        sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
      • Em seguida, atualize o banco de dados de pacotes com os pacotes Docker do repo recém-adicionado:

        sudo apt update
      • Certifique-se de que está prestes a instalar a partir do repositório Docker em vez do repositório Ubuntu padrão:

        apt-cache policy docker-ce

        Observe que docker-ce não está instalado, mas o candidato para instalação é do repositório Docker para Ubuntu 20.04 (focal). Finalmente, instale o Docker:

        sudo apt install docker-ce
      • O Docker agora deve estar instalado, o daemon iniciado e o processo habilitado para iniciar na inicialização. Verifique se ele está funcionando:

        • LINUX:
          sudo systemctl status docker 
        • WSL2:
          sudo /etc/init.d/docker status 

      1.1.2 — Executando o comando Docker sem Sudo (opcional)

      • Se quiser evitar digitar sudo sempre que executar o comando docker, adicione seu nome de usuário ao grupo docker:

        sudo usermod -aG docker ${USER}
      • Para aplicar a nova associação de grupo, saia do servidor e entre novamente ou digite o seguinte:

        Você será solicitado a inserir sua senha de usuário para continuar.

        su - ${USER}
      • Confirme se o seu usuário foi adicionado ao grupo docker digitando:

        id -nG

        Output: sammy sudo docker

      • listar docker e rodar teste:

        docker ps
        docker run hello-world
      • Instale também o docker compose (utilizaremos 1.28.2)

        sudo curl -L "https://github.com/docker/compose/releases/download/1.28.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
      • dando permissão de execução para docker-compose:

        sudo chmod +x /usr/local/bin/docker-compose
      • Verificando versão:

        docker-compose --version
  • Instalando Portainer.io:

    Instalando Portainer.io:

    2 — Instalando Portainer.io

    Iremos instalar o portainer.io para termos uma visualização dos containers via UI:

    • Criando volume:
        docker volume create portainer_data
    • Instalando portainer no volume:
        docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
    • Para acessar: http://localhost:9000/
  • Entendendo e Configurando Docker Composer no projeto:

    Entendendo e Configurando Docker Composer no projeto:

    3.1. Comandos mais utilizados:

    Antes de utiliza-los, devemos estar no diretório, no terminal:

    • Iniciar serviço docker:
      • WSL: sudo /etc/init.d/docker start;
      • Linux: sudo systemctl start docker;
    • Iniciar: docker-compose up -d;
    • Listar containers: docker ps;
    • Derrubar os container e remover os volumes: docker-compose down.

    3.2. Serviços no container:

    • Iremos criar um diretório docker em nosso projeto e criaremos o arquivo de configuração docker-compose.yml.

    • Antes, temos que identificar o que queremos conteinerizar. Para este projeto será apenas nosso banco de dados;

    • Vamos entender nosso docker-compose.yml:

        version: '3.7'
        services:
          crud-database:
            # image to fetch from docker hub
            image: postgres:latest
      
            # Environment variables for startup script
            # container will use these variables
            # to start the container with these define variables. 
            environment:
              POSTGRES_PASSWORD: admin
              POSTGRES_USER: admin
              POSTGRES_DB: crudDB
            container_name: crud-database
      
            # Mapping of container port to host
            ports:
              - "5432:5432"
      • version ‘3.7’: Isso indica que estamos usando a versão 3.7 do Docker Compose, e o Docker fornecerá os recursos apropriados.

      • services: Esta seção define todos os diferentes contêineres que criaremos. Em nosso projeto, temos cinco serviços ( dois bancos, kafka, etc).

      • database-checkout: Este é o nome do nosso serviço de banco de dados. O Docker Compose criará contêineres com o nome que fornecemos.

      • image: Se não tivermos um Dockerfile e quisermos executar um serviço usando uma imagem pré-construída, especificamos o local da imagem usando a cláusula image. O Compose fará um fork de um contêiner dessa imagem.

      • ports: Isso é usado para mapear as portas do contêiner para a máquina host.

      • environment: A cláusula nos permite configurar uma variável de ambiente no contêiner. É o mesmo que o argumento -e no Docker ao executar um contêiner.

      • Os parâmetros POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB, indicam ao docker, para inicializar nosso banco de dados com o usuário de conexão pré-configurado.


  • Mais Informações:

    Docker in 100 Seconds


2. PostgreSQL:

Banco de Dados Relacional:

Um banco de dados relacional é um banco de dados que modela os dados de uma forma que eles sejam percebidos pelo usuário como tabelas, ou mais formalmente relações..

  • O que é:

    • PostgreSQL, trata-se de um SGBD relacional, orientado a objetos, em que objetos definidos pelo usuário e a abordagem de tabela são combinados para criar estruturas mais complexas de dados. Além disso, o PostgreSQL tem muitas semelhanças com o MySQL. Suporta nativamente um grande número de categorias de dados padrão, como JSON, XML, etc.
      • É um dos poucos bancos de dados relacionais que oferece forte suporte para a funcionalidade NoSQL.
      • Permite que os usuários definam seus próprios tipos de dados.
      • A escalabilidade vertical é um recurso do PostgreSQL. Como quase todas as soluções de ‘software’ personalizadas tendem a crescer, resultando na expansão do banco de dados, essa opção oferece suporte ao crescimento e ao desenvolvimento dos negócios muito bem.
      • Oferece suporte a ferramentas adicionais, tanto gratuitas quanto comerciais.
  • Configurando e conectando PostgreSQL no projeto:

    Configurando e conectando PostgreSQL no projeto:

    1 - Conectando banco:

    • Após configurado nosso container seguindo os passos do item anterior, iremos conectar o banco à nossa aplicação:

      • Primeiramente, iremos adicionar as seguintes linhas de dependência em nosso arquivo build.gradle

            dependencies {
                // PostgreSQL
                runtimeOnly 'org.postgresql:postgresql'
            }
        
      • Em seguida, em application.yml, iremos colocar as propriedades com dados de conexão:

        spring:
        datasource:
          url: jdbc:postgresql://localhost:5432/crudDB
          username: admin
          password: admin
          driver-class-name: org.postgresql.Driver
          hikari:
            connection-test-query: select 1
      • Desta forma, com o container inicializado, ao rodar a aplicação, teremos nossa conexão entre aplicação e banco de dados.


  • Mais Informações:

    Curso de PostgreSQL - Bóson Treinamentos


3. ORM:

  • O que é:

    • ORM é um acrônimo para "Object-Relational Mapping", sendo uma categoria de tecnologia que permite que um aplicativo manipule dados em uma base de dados de maneira mais simples e intuitiva, usando objetos e classes em vez de instruções SQL diretas.
      • JPA(Java Persistence API): É uma especificação de persistência de objetos que pode ser usada para implementar uma ORM. Ela define uma maneira de mapear objetos do Java para tabelas em uma base de dados, bem como uma maneira de recuperar e armazenar esses objetos de volta na base de dados. JPA é uma especificação e não fornece uma implementação concreta dessa API.
      • Hibernate: Já o Hibernate é uma biblioteca que fornece uma implementação concreta da API JPA, permitindo que os desenvolvedores usem o Hibernate para mapear objetos do Java para tabelas em uma base de dados e recuperar e armazenar esses objetos de volta na base de dados. Ou seja, pode ser usada como uma ORM. Hibernate é uma das opções mais populares e amplamente utilizadas.
  • Configurando e Implementando JPA no projeto:

    Configurando e Implementando JPA no projeto:

    Para configurar o JPA com Hibernate em nosso projeto, seguiremos os seguintes passos:

    • Adicionaremos as dependências do JPA e do PostgreSQL ao arquivo build.gradle do projeto.
        dependencies {
          // Spring Boot
          implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    
          // PostgreSQL
          runtimeOnly 'org.postgresql:postgresql'
        }
    • Criaremos um arquivo application.yml na raiz do projeto com as configurações de conexão com o banco de dados.
        spring:
          datasource:
            url: jdbc:postgresql://localhost:5432/crudDB
            username: admin
            password: admin
            driver-class-name: org.postgresql.Driver
            hikari:
              connection-test-query: select 1
          jpa:
            hibernate:
              ddl-auto: create
            properties:
              hibernate:
                dialect: org.hibernate.dialect.PostgreSQLDialect
                format_sql: true
                show_sql: true
                use_sql_comments: true
                jdbc:
                  lob:
                    non_contextual_creation: true
    • Em seguida criamos nossa entidade que será nossa camada responsável por mapear os objetos do Java para tabelas no banco de dados.
      • Anotaremos a classe com @Entity e iremos mapear os atributos para as colunas da tabela usando anotações como @Column.
        @Entity
        public class Student {
      
        @Id
        @Column(nullable = false)
        @GeneratedValue(strategy = SEQUENCE, generator = "STUDENT_SEQ")
        @EqualsAndHashCode.Include
        private Long id;
        
            @Column(nullable = false)
            @NotEmpty(message = "Nome não informado")
            @Length(max = 10, message = "Nome muito extenso")
            private String name;
        // Getters e setters serão gerados pelo lombok (@Getter e @Setter)
        //[...]
        }
    • Agora, vamos criar a camada de repository, que será responsável por acessar os dados no banco de dados. Para isso, crie uma interface que estenda JpaRepository e especifique o tipo de entidade e o tipo de chave que ela utiliza. Por exemplo:
        public interface StudentRepository extends JpaRepository<Student, Long> {
        }
    • Para utilizar iremos realizar a injeção no Service desejado via construtor parametrizado.
      • Como estamos utilizando lombok anotaremos nosso Service com @RequiredArgsConstructor;
        @Service
        @RequiredArgsConstructor
        @Slf4j
        public class StudentService {
            private final StudentRepository studentRepository;
            //[...]
        }
    • Com essas etapas configuradas, o JPA estará pronto para ser usado no projeto Spring Boot com Gradle. Você poderá acessar os dados no banco de dados usando os métodos do repositório, como findById(), findAll(), save() e delete(). Além disso, você também pode criar consultas personalizadas usando a anotação @Query na interface de repositório. Por exemplo:
        public interface PessoaRepository extends JpaRepository<Pessoa, Long> {
          @Query("SELECT p FROM Pessoa p WHERE p.nome = :nome")
          List<Pessoa> findByNome(@Param("nome") String nome);
        }
    • Essa consulta personalizada pode ser usada da seguinte maneira:
        List<Pessoa> pessoas = repository.findByNome("Robson");
    • É importante lembrar ser necessário criar as tabelas no banco de dados manualmente ou automático (ddl-auto = create) antes de tentar acessá-las usando o JPA.
  • Mais Informações:

    Learn JPA & Hibernate


4. LOMBOK:

  • O que é:

    • Lombok é uma biblioteca de código aberto para Java que fornece uma série de anotações para gerar automaticamente código boilerplate, como getters, setters, construtores e métodos toString(), equals() e hashCode(). Isso pode tornar o processo de desenvolvimento de aplicativo Java mais rápido e mais fácil, pois permite que os desenvolvedores evitem escrever código repetitivo manualmente.
  • Configurando e Implementando Lombok no projeto:

    Configurando e Implementando Lombok no projeto:

    Para adicionar o Lombok ao projeto, basta adicionar a dependência do Lombok ao arquivo build.gradle do projeto da seguinte forma:

        dependencies {
          // Lombok
          compileOnly 'org.projectlombok:lombok'
          annotationProcessor 'org.projectlombok:lombok'
        }
    • Desta forma podemos usar as anotações do Lombok em nossas classes. Por exemplo, como em nossa classe Student, para gerar getters e setters automaticamente, adicionaremos a anotação @Getter e @Setter (removendo o Setter apenas do id):
    //[...] outras anotações
    @Getter
    @Setter
    public class Student {
    
        @Setter(AccessLevel.NONE)
        private Long id;
    
        private String name;
    
        private String lastName;
    
        private Integer age;
    
    }
  • Entendendo um pouco mais sobre Lombok:

    Anotações mais utilizadas do Lombok:

    Abaixo temos algumas das anotações mais utilizadas do Lombok:

    • @Getter: Gera getters automaticamente para todos os atributos da classe.
    • @Setter: Gera setters automaticamente para todos os atributos da classe.
    • @ToString: Gera um método toString() automaticamente que retorna uma ‘string’ com os valores de todos os atributos da classe.
    • @EqualsAndHashCode: Gera os métodos equals() e hashCode() automaticamente conforme as regras padrão do Java.
    • @NoArgsConstructor: Gera um construtor sem argumentos automaticamente.
    • @AllArgsConstructor: Gera um construtor com argumentos para todos os atributos da classe.
    • @RequiredArgsConstructor: Gera um construtor com argumentos apenas para os atributos marcados com @NonNull.
    • @Data: Inclui todas as anotações @Getter, @Setter, @ToString, @EqualsAndHashCode e @RequiredArgsConstructor em uma só, permitindo gerar getters, ‘setters’, um método toString(), métodos equals() e hashCode() e um construtor com argumentos para os atributos marcados com @NonNull automaticamente.
    • @Value: Similar à anotação @Data, mas cria uma classe imutável em vez de uma classe mutável. Isso significa que os ‘setters’ não são gerados e os atributos só podem ser atribuídos no construtor.
    • @Builder: Gera um builder para a classe, permitindo criar objetos da classe de forma fluida.
    • @SneakyThrows: Permite lançar exceções checked de forma silenciosa, sem precisar declará-las no método.
    • @Synchronized: Adiciona sincronização a um método, permitindo que ele seja chamado por apenas uma thread de cada vez.
    • @Log: Gera uma variável de log para a classe, permitindo logar mensagens usando o log4j ou outro framework de log.
  • Mais Informações:

    Project Lombok

    Java 14 Record vs. Lombok

    Introduction to Project Lombok


5. Testes:

  • Retomando o assunto:

    • No projeto anterior abordamos as configurações do Junit5 e Cucumber. Neste, iremos entrar em alguns detalhes de implementação que nos leva a um novo passo devido o aumento da complexidade da aplicação em questão.
  • Mergulhando no JUnit5 | Mockito:

    Um pouco sobre Mockito:

    Mockito é uma biblioteca de mocks para Java que permite criar objetos "falsos" ou "simulados" para testar o código. Os mocks são usados para simular o comportamento de dependências ou colaboradores de uma classe, permitindo que os testes sejam isolados e focados em uma única unidade de código.

    Isso é especialmente útil quando precisamos testar código que depende de outras classes ou componentes que não estão disponíveis ou são difíceis de testar, como banco de dados, APIs externas ou componentes de sistema.

    • Para usar o Mockito em um projeto JUnit 5, basta adicionar a dependência do Mockito ao arquivo build.gradle do projeto e importar a classe MockitoExtension no seu arquivo de teste. Por exemplo:
    • Podemos observar a utilização do mock em nossa classe de teste StudentServiceTest
  • Mergulhando no JUnit5 | Factory:

    Um pouco sobre Factory:

    Factory são classes que criam objetos de outras classes de acordo com alguns parâmetros ou configurações. Isso é útil para evitar a repetição de código na criação de objetos e para facilitar a configuração de objetos para testes.

    • Temos algumas factories criadas no projeto como: StudentFactory, StudentRequestDtoFactory e StudentResponseDtoFactory.
    • Podemos observar a utilização do factory em nossa classe de teste StudentServiceTest
  • Mergulhando no Cucumber ( ͡° ͜ʖ ͡°) | Datatable:

    Um pouco sobre Datatable:

    Anteriormente no projeto hello-world, vimos como configurar o cucumber no projeto. Agora iremos complementar com uma técnica de passagem de dados, principalmente quando queremos representar tableas ou objetos, o Datatable.

    Sempre devemos utilizar o datable para passagem de dados/ "variaveis", pois o cucumber é na teoria vizualizado pela equipe de QA e Negócios.

    • Desta forma facilitando a leitura e mudança de dados para teste
    • Pode também ser integrado com algumas ferramentas como, por exemplo, o jira cucucmber integration

    No projeto podemos ver como é montado um cenário de teste utilizando datatable:

    Cenário: Adiciona dado de um novo estudante
      Dado que possuo as informações do estudante
        | name   | last_name | age |
        | Ragnar | L         | 18  |
      Quando envio as informações do meu estudante
      Então deverá ser cadastrado com sucesso

    Como o datatable é uma entrada de dado crua, precisamos criar um adaptador para utilizar esses dados. Desta forma, criamos na classe StudentDataTableAdapter um tratamento para criar um StudentRequestDto apartir dos dados informados. Assim podendo ser utilizado normalmente na implementação dos Steps.

      public List<StudentRequestDto> createStudentList(final DataTable dataTable) {
          return dataTable
                  .asMaps(String.class, String.class)
                  .stream()
                  .map(column -> {
                      return new StudentRequestDto(column.get(NAME), column.get(LAST_NAME), Integer.parseInt(column.get(AGE)));
                  }).collect(Collectors.toList());
      }
  • Mais Informações:

    Complete JUnit 5 Mockito Tutorial For Unit Testing

    Cucumber Data Tables - baeldung


Tecnologias a serem estudadas em projetos futuros:

  • Json Patch
  • Spring Auth
  • Apache Kafka
  • Hystrix
  • Spark ( e TDD para RDD)
  • AWS (AWS LocalStack)
    • S3
    • EC2
  • Kubernetes
  • Sleuth
  • Zipkin UI
  • Spring Cloud Config (Remote Configuration)
  • Spring Cloud Bus
  • Migrations (FlyWay vs Liquibase)
  • Aplicar separação da camada de ORM(framework) da Entidade

Como Contribuir?

Para organizar o nosso fluxo de trabalho, seguimos de forma conceitual o modelo do Git Flow. Os passos para contribuir com o projeto são:

  • Criar uma feature branch a partir da develop;
  • Desenvolver o que precisar nesta nova branch;
  • Criar um Pull Request;
  • Após a aprovação do Pull Request, a sua versão deve ser mergeada na develop.

É importante seguir os padrões de commit semântico

Prefixos para commit:
  • build: Alterações que afetam o sistema de construção ou dependências externas (escopos de exemplo: gulp, broccoli, npm),
  • ci: Mudanças em nossos arquivos e scripts de configuração de CI (escopos de exemplo: Travis, Circle, BrowserStack, SauceLabs);
  • docs: referem-se a inclusão ou alteração somente de arquivos de documentação;
  • feat: Tratam adições de novas funcionalidades ou de quaisquer outras novas implantações ao código;
  • fix: Essencialmente definem o tratamento de correções de bugs;
  • perf: Uma alteração de código que melhora o desempenho;
  • refactor: Tipo utilizado em quaisquer mudanças que sejam executados no código, porém não alterem a funcionalidade final da tarefa impactada;
  • style: Alterações referentes a formatações na apresentação do código que não afetam o significado do código, como por exemplo: espaço em branco, formatação, ponto e vírgula ausente etc.);
  • test: Adicionando testes ausentes ou corrigindo testes existentes nos processos de testes automatizados (TDD);
  • chore: Atualização de tarefas que não ocasionam alteração no código de produção, mas mudanças de ferramentas, mudanças de configuração e bibliotecas que realmente não entram em produção;
  • env: basicamente utilizado na descrição de modificações ou adições em arquivos de configuração em processos e métodos de integração contínua (CI), como parâmetros em arquivos de configuração de containers.
  • improvement: commits que melhoram uma implementação atual sem adicionar um novo recurso ou consertar um bug.