DEV Community

Meu Código Ágil
Meu Código Ágil

Posted on

JHipster 8 - Analisando o código da nossa primeira aplicação monolítica - Parte 2/3

Dando continuidade à nossa análise do código-fonte da aplicação monolítica que criamos utilizando o JHipster, vamos agora explorar o código Java do nosso backend. É importante lembrar que o código do backend é baseado no framework Spring Boot. No caso do JHipster 8, no Spring Boot 3.2.3 mais especificamente.

Conforme mencionado no primeiro post desta sequência voltada para a análise do código do nosso monolito, o volume de código gerado é bastante extenso, o que torna inviável abordar todos os detalhes sem se tornar cansativo. Portanto, selecionei criteriosamente os pacotes e classes que julgo mais importantes neste estágio em que nos encontramos nesta jornada de apresentação do JHipster 8.

Java

MinhaAplicacaoMonoliticaApp.java

Conforme definido durante os prompts do JHipster CLI ao construirmos nossa aplicação, o pacote base das classes da aplicação é br.com.meucodigoagil.minhapp, assim, nosso código fonte Java estará localizado no diretório src/main/java/br/com/meucodigoagil/minhapp e é exatamente aí que estará a classe principal da nossa aplicação: MinhaAplicacaoMonoliticaApp.

Como uma aplicação Spring Boot, é pela sua classe principal que a aplicação é inicializada. Esta classe deve ser anotada com @SpringBootApplication.

@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
public class MinhaAplicacaoMonoliticaApp {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Como uma classe principal, podemos notar que ela também conta com um método estático main que cria uma instância de SpringApplication passando a própria classe como parâmetro, executa a instância criada e imprime no console aquela mensagem que vimos ao inicializar a nossa aplicação informando, entre outros, host e porta.

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MinhaAplicacaoMonoliticaApp.class);
    DefaultProfileUtil.addDefaultProfile(app);
    Environment env = app.run(args).getEnvironment();
    logApplicationStartup(env);
}
Enter fullscreen mode Exit fullscreen mode

br.com.meucodigoagil.minhapp.config.*

Dentro do pacote br.com.meucodigoagil.minhapp.config, você poderá conferir um grande número de classes destinadas a configurar a nossa aplicação.

A classe CacheConfiguration habilita cache na nossa aplicação e configura o Ehcache conforme selecionamos durante os prompts do JHipster CLI.

@Configuration
@EnableCaching
public class CacheConfiguration {

    private GitProperties gitProperties;
    private BuildProperties buildProperties;
    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

    public CacheConfiguration(JHipsterProperties jHipsterProperties) {
        JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache();

        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(
                Object.class,
                Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries())
            )
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
                .build()
        );
    }
Enter fullscreen mode Exit fullscreen mode

A classe DatabaseConfiguration possui um bean que configura servidor do banco de dados H2 (usado em tempo de desenvolvimento) para escutar em uma porta TCP.

Perceba no código a seguir que a anotação @Profile restringe a aplicação desta configuração apenas quando a aplicação está sendo executada sob o perfil de desenvolvimento.

@Bean(initMethod = "start", destroyMethod = "stop")
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
public Object h2TCPServer() throws SQLException {
    String port = getValidPortForH2();
    log.debug("H2 database is available on port {}", port);
    return H2ConfigurationHelper.createServer(port);
}
Enter fullscreen mode Exit fullscreen mode

A classe SecurityConfiguration faz uma série de configurações relacionadas à segurança da aplicação, como a liberação ou a restrição de certos endpoints. Enquanto a SecurityJwtConfiguration cria beans relacionados à autenticação por JWT, incluindo codificador e decodificador do token.

@Configuration
public class SecurityJwtConfiguration {

    private final Logger log = LoggerFactory.getLogger(SecurityJwtConfiguration.class);

    @Value("${jhipster.security.authentication.jwt.base64-secret}")
    private String jwtKey;

    @Bean
    public JwtDecoder jwtDecoder(SecurityMetersService metersService) {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build();
        return token -> {
            try {
                return jwtDecoder.decode(token);
            } catch (Exception e) {
                if (e.getMessage().contains("Invalid signature")) {
                    metersService.trackTokenInvalidSignature();
                } else if (e.getMessage().contains("Jwt expired at")) {
                    metersService.trackTokenExpired();
                } else if (
                    e.getMessage().contains("Invalid JWT serialization") ||
                    e.getMessage().contains("Malformed token") ||
                    e.getMessage().contains("Invalid unsecured/JWS/JWE")
                ) {
                    metersService.trackTokenMalformed();
                } else {
                    log.error("Unknown JWT error {}", e.getMessage());
                }
                throw e;
            }
        };
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        return new NimbusJwtEncoder(new ImmutableSecret<>(getSecretKey()));
    }
    ...
Enter fullscreen mode Exit fullscreen mode

br.com.meucodigoagil.minhapp.domain.*

No pacote br.com.meucodigoagil.minhapp.domain encontraremos nossas classes de domínio, ou seja, as entidades da nossa aplicação anotadas com @Entity . Além das entidades que fazem parte do domínio da nossa aplicação, como a nossa Produto, encontraremos entidades relacionadas a funcionalidades de segurança criadas automaticamente pelo JHipster, como User e Authority.

Ao analisarmos a classe Produto, podemos notar que o atributo nome possui a anotação com as regras de tamanho que solicitamos ao JHipster CLI.

@Entity
@Table(name = "produto")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@SuppressWarnings("common-java:DuplicatedBlocks")
public class Produto implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    @Column(name = "id")
    private Long id;

    @NotNull
    @Size(min = 3, max = 100)
    @Column(name = "nome", length = 100, nullable = false, unique = true)
    private String nome;

    ...
}
Enter fullscreen mode Exit fullscreen mode

br.com.meucodigoagil.minhapp.repository.*

Neste pacote temos as classes que farão o papel de repositório de dados, ou seja, que terão a responsabilidade de buscar os dados no banco de dados e que são anotadas com @Repository. Temos repositório para cada uma das classes de domínio. Perceba que se trata apenas de uma interface que, além de anotada, estende a interface JpaRepository do Spring, que proverá por baixo dos panos um conjunto de métodos básicos de acesso e manutenção de dados.

@Repository
public interface ProdutoRepository extends JpaRepository<Produto, Long> {}
Enter fullscreen mode Exit fullscreen mode

br.com.meucodigoagil.minhapp.service.*

Neste pacote ficam as classes @Service que, no Spring, são responsáveis pela implementação de regras de negócio, bem como as classes DTO. Ao visualizar as classes deste pacote na nossa aplicação, você notará que se trata apenas do UserService, relacionada à entidade User padrão do JHipster e MailService, além de algumas classes Exception. Isso se deve ao fato de que durante o prompt do JHipster CLI nós escolhemos que as nossas classes @RestController chamariam as @Repository de forma direta. Lembra?

br.com.meucodigoagil.minhapp.web.*

Neste pacote, teremos a implementação de classes do tipo Filter e Controllers do nosso MVC. Falando em controllers, eles estão mais especificamente no pacote br.com.meucodigoagil.minhapp.web.rest.

Encontramos o controller que expõe endpoints da nossa entidade Produto e também outros controllers com endpoints voltados para mecanismos de autenticação e autorização da aplicação.

Conforme citado anteriormente, podemos ver no código que a ProdutoResource tem injetada uma instância de ProdutoRepository em seu construtor e que este será invocado, por exemplo, no método getAllProdutos().

@RestController
@RequestMapping("/api/produtos")
@Transactional
public class ProdutoResource {

    private final Logger log = LoggerFactory.getLogger(ProdutoResource.class);

    private static final String ENTITY_NAME = "produto";

    @Value("${jhipster.clientApp.name}")
    private String applicationName;

    private final ProdutoRepository produtoRepository;

    public ProdutoResource(ProdutoRepository produtoRepository) {
        this.produtoRepository = produtoRepository;
    }

    /**
     * {@code GET  /produtos} : get all the produtos.
     *
     * @return the {@link ResponseEntity} with status {@code 200 (OK)}
     * and the list of produtos in body.
     */
    @GetMapping("")
    public List<Produto> getAllProdutos() {
        log.debug("REST request to get all Produtos");
        return produtoRepository.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

Resources

Além dos arquivos Java, também fazem parte do backend os arquivos de configuração, que ficam sob o diretório src/main/resources. Passemos pelos principais pontos.

config/application*.yml

Na raiz do diretório src/main/resources/config, temos um conjunto de arquivos YML para configuração da nossa aplicação. O arquivo application.yml, detêm as configurações aplicáveis a qualquer que seja o profile sob o qual a aplicação está em execução, seja desenvolvimento ou produção.

Já os arquivos com algum sufixo, são aplicados de acordo com o profile sob o qual a aplicação está em execução. Assim, o arquivo application-dev.yml armazenará valores de configuração especificamente para quando a aplicação está executando sob o perfil dev. Note que sob a chave spring.datasource encontramos uma configuração do banco de dados H2:

spring:
  ...
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:file:./target/h2db/db/minhaAplicacaoMonolitica;DB_CLOSE_DELAY=-1
    username: minhaAplicacaoMonolitica
    password:
    hikari:
      poolName: Hikari
      auto-commit: false
  h2:
    console:
      # disable spring boot built-in h2-console since we start it manually with correct configuration
      enabled: false
Enter fullscreen mode Exit fullscreen mode

Por outro lado, o application-prod.yml será utilizado quando a aplicação estiver sob o profile prod. Se verificarmos a mesma chave spring.datasource, encontraremos a configuração para uma base PostgreSQL conforme nós escolhemos em um dos prompts do JHipster CLI:

spring:
  ...
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:postgresql://localhost:5432/minhaAplicacaoMonolitica
    username: minhaAplicacaoMonolitica
    password:
    hikari:
      poolName: Hikari
      auto-commit: false
Enter fullscreen mode Exit fullscreen mode

config/liquibase/

Sob o diretório src/resources/config/liquibase, temos os changelogs do Liquibase, a ferramenta utilizada pelas aplicações geradas pelo JHipster para o gerenciamento de mudanças no esquema do banco de dados.

No subdiretório changelog, temos os arquivos com os conjunto de instruções para criação e modificação do esquema de banco de dados conforme podemos ver a seguir a criação da tabela Produto:

<changeSet id="20240324155937-1" author="jhipster">
    <createTable tableName="produto">
        <column name="id" type="bigint">
            <constraints primaryKey="true" nullable="false"/>
        </column>
        <column name="nome" type="varchar(100)">
            <constraints nullable="false" unique="true" uniqueConstraintName="ux_produto__nome" />
        </column>
        <column name="tipo" type="varchar(255)">
            <constraints nullable="false" />
        </column>
        <!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
    </createTable>
</changeSet>
Enter fullscreen mode Exit fullscreen mode

Já na raiz, temos no arquivo master.xml, além de declarar algumas propriedades que podem ser utilizadas dentro dos changelogs, também faz referência aos próprios changelogs que serão executados:

<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
    <property name="now" value="now()" dbms="h2"/>
    ...

    <include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
    <include file="config/liquibase/changelog/20240324155937_added_entity_Produto.xml" relativeToChangelogFile="false"/>
</databaseChangeLog>
Enter fullscreen mode Exit fullscreen mode

config/i18n/

Sob este diretório encontramos arquivos de propriedades com mensagens nos idiomas que selecionamos para a aplicação. A seguir, mostramos parcialmente os arquivos com mensagens em português e em inglês:

 # Reset email
email.reset.title=A Senha da aplicação minhaAplicacaoMonolitica foi redifinida
email.reset.greeting=Caro {0}
email.reset.text1=Foi solicitado a redefinição de senha da sua conta minhaAplicacaoMonolitica. Por favor clique na url abaixo para alterá-la:
email.reset.text2=Atenciosamente,
Enter fullscreen mode Exit fullscreen mode
 # Reset email
# Reset email
email.reset.title=minhaAplicacaoMonolitica password reset
email.reset.greeting=Dear {0}
email.reset.text1=For your minhaAplicacaoMonolitica account a password reset was requested, please click on the URL below to reset it:
email.reset.text2=Regards,
Enter fullscreen mode Exit fullscreen mode

Os textos contidos nas chaves desses arquivos serão lidos com auxílio do Spring Boot para retornar textos de acordo com o idioma do usuário final. A seguir um exemplo de como isso poderia ser realizado considerando que os arquivos de mensagens tivessem uma mensagem sob a chave error.message:

public class ApenasExemplo{

  private final MessageSource messageSource;

  public ApenasExemplo(MessageSource messageSource){
    this.messageSource = messageSource;
  }

  public void facaAlgo(User user){
    Locale locale = Locale.forLanguageTag(user.getLangKey());
    throw new RuntimeException(
      messageSource.getMessage("error.message", null, locale)
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Finalizamos aqui a análise do código backend gerado pelo JHipster. Passamos pelas principais classes Java da aplicação, classes do domínio, classes de configuração, classes de acesso a dados, controllers, e também pelos arquivos de configuração do nosso backend.

Na próxima publicação, vamos nos aprofundar no código-fonte do nosso frontend Angular. Até lá, explore mais o código do nosso backend e mande suas dúvidas nos comentários ;)

Top comments (0)