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 {
...
}
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);
}
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()
);
}
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);
}
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()));
}
...
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;
...
}
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> {}
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();
}
}
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
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
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>
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>
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,
# 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,
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)
);
}
}
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)