Microservices, Docker e Tecnologias de Mensageria parte 4
Hernani Almeida
Posted on March 1, 2022
Ferramentas necessárias:
Bora galera para nossa ultima api que ira compor nosso sistema de microservices.
Crie uma aplicação com o nome de sua escolha no spring starter com as seguintes dependências necessárias
Para continuarmos verifique se o arquivo pom.xml da sua aplicação possui todas as dependências conforme o arquivo abaixo.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>br.com</groupId>
<artifactId>consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Feito isso precisamos configurar em nosso arquivo application.properties
nosso banco de dados postgres
e o acesso a nossa fila activemq
.
application.properties
# datasource
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/testePostgres
spring.datasource.username=bootcamp
spring.datasource.password=password
# jpa
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
# Mostrar Sql no terminal
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
server.error.include-message=always
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin
server.port=8082
Criamos agora as classes, JmsConfig
para configurar o JmsListener e a connection com nosso broker activemq e a RedisConfig
para configurar nosso acesso ao banco de memoria Redis de nossa aplicação.
JmsConfig
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.ConnectionFactory;
@Configuration
@EnableJms
public class JmsConfig {
@Value("${spring.activemq.broker-url}")
private String brokerUrl;
@Value("${spring.activemq.user}")
private String user;
@Value("${spring.activemq.password}")
private String password;
@Bean
public ActiveMQConnectionFactory connectionFactory() {
if ( "".equals(user) ) {
return new ActiveMQConnectionFactory(brokerUrl);
}
return new ActiveMQConnectionFactory(user, password, brokerUrl);
}
@Bean
public JmsListenerContainerFactory jmsFactoryTopic(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
@Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(connectionFactory());
}
@Bean
public JmsTemplate jmsTemplateTopic() {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
jmsTemplate.setPubSubDomain( true );
return jmsTemplate;
}
}
RedisConfig
import br.com.consumer.user.UserRedis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class RedisConfig {
@Bean
public JedisConnectionFactory connectionJedisFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost", 6379);
return new JedisConnectionFactory(config);
}
@Bean
RedisTemplate<String, UserRedis> redisTemplate(){
RedisTemplate<String,UserRedis> redisTemplate = new RedisTemplate<String, UserRedis>();
redisTemplate
.setConnectionFactory(connectionJedisFactory());
return redisTemplate;
}
}
Feito isso foi criado uma classe Dto para que possamos serializar a mensagem recebida pela aplicação anterior em um objeto Java e as classes, UserRedis
e UserRedisRepository
, utilizadas para que possamos salvar os dados do usuário no Redis
.
UserDto
import lombok.Data;
import org.hibernate.validator.constraints.br.CPF;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import java.util.UUID;
@Data
public class UserDto {
@NotBlank
private String name;
@Email
@NotBlank
private String email;
@CPF
@NotBlank
private String cpf;
public UserDto(String name, String email, String cpf) {
this.name = name;
this.email = email;
this.cpf = cpf;
}
public UserDto() {
}
public UserRedis converteRedis() {
return UserRedis.builder()
.id(UUID.randomUUID().toString())
.name(name)
.cpf(cpf)
.email(email)
.build();
}
}
UserRedis
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserRedis implements Serializable {
private String id;
private String name;
private String email;
private String cpf;
public static List<Object> retornaListaUsuarios(Map<String, UserRedis> users) {
List<Object> userRedisLista = new ArrayList<>();
Iterator userIterator = users.entrySet().iterator();
while (userIterator.hasNext()) {
Map.Entry mapElement = (Map.Entry)userIterator.next();
userRedisLista.add(mapElement.getValue());
}
return userRedisLista;
}
public UserRedis converteRedis(UserRedis usuario) {
UserRedis usuarioDatabase = UserRedis
.builder()
.name(usuario.getName())
.email(usuario.getEmail())
.cpf(usuario.getCpf())
.build();
return usuarioDatabase;
}
public UserPostgres converte() {
return new UserPostgres(name,email,cpf);
}
}
UserRedisRepository
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public class UserRedisRepository {
public static final String KEY = "REDISUSER";
private RedisTemplate<String,UserRedis> redisTemplate;
private HashOperations hashOperations;
public UserRedisRepository(RedisTemplate<String, UserRedis> redisTemplate) {
this.redisTemplate = redisTemplate;
hashOperations = redisTemplate.opsForHash();
}
/*Getting all Items from tSable*/
public List<Object> getAllUsers(){
Map<String,UserRedis> users = hashOperations.entries(KEY);
List<Object> userRedisList = UserRedis.retornaListaUsuarios(users);
return userRedisList;
}
/*Getting a specific item by item id from table*/
public UserRedis getUser(String userId){
return (UserRedis) hashOperations.get(KEY,userId);
}
/*Adding an item into redis database*/
public void addUser(UserRedis user){
hashOperations.put(KEY,user.getId(),user);
}
/*delete an item from database*/
public void deleteItem(String id){
hashOperations.delete(KEY,id);
}
/*update an item from database*/
public void updateItem(UserRedis user){
addUser(user);
}
}
Criamos agora a classe JmsServiceImpl
que utiliza a annotation @JmsListener passando como parametro nossa fila criada na aplicação anterior, essa classe ficara ouvindo nossa fila e consumindo todas as mensagens produzidas transformando-as em um objeto Java e salvando no Redis.
JmsServiceImpl
import br.com.consumer.user.UserDto;
import br.com.consumer.user.UserRedis;
import br.com.consumer.user.UserRedisRepository;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.jms.annotation.JmsListener;
@Component
public class JmsServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(JmsServiceImpl.class);
@Autowired
private Gson serializer;
@Autowired
private UserRedisRepository redisRepository;
@JmsListener(destination = "queue.sample")
public void onReceiverQueue(String message) {
logger.info("message received: {}", message);
UserDto usuarioDto = serializer.fromJson(message, UserDto.class);
UserRedis usuario = usuarioDto.converteRedis();
logger.info(usuario.toString());
redisRepository.addUser(usuario);
}
}
Bora finalizar nossa aplicação, para isso criamos as classes, User
(entidade do banco de dados) e a UserRepository
utilizar os metodos do JPA para salvar os dados do usuario no banco de dados Postgres.
UserPostgres
import lombok.Data;
import org.hibernate.validator.constraints.br.CPF;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
@Data
@Entity
public class UserPostgres {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@NotBlank
private String name;
@Email
@NotBlank
@Column(unique = true)
private String email;
@CPF
@NotBlank
@Column(unique = true)
private String cpf;
public Long getUserId() {
return userId;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getCpf() {
return cpf;
}
@Deprecated
public UserPostgres() {
}
public UserPostgres(String name, String email, String cpf) {
this.name = name;
this.email = email;
this.cpf = cpf;
}
}
UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<UserPostgres, Long> {
Optional<UserPostgres> findByName(String name);
}
Feito isso criamos a classe UserController
que ira salvar definitivamente nosso usuário no banco de dados Postgres após o mesmo ser aceito pelo front, vale dar uma explicação sobre o sentido abordado aqui, salvamos o usuário em um primeiro momento no Redis onde será listado no front a intenção do cadastro, o admin do sistema aceitando o cadastro e realizada a chamada ao nosso controller para que este usuário seja salvo no banco de dados Postgres e excluido do Redis, caso o admin nao aceita o cadastro o mesmo so exclui o cadastro do Redis.
UserController
import br.com.consumer.user.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private UserService userService;
private UserRedisRepository redisRepository;
public UserController(UserService userService, UserRedisRepository redisRepository) {
this.userService = userService;
this.redisRepository = redisRepository;
}
@GetMapping("redis/user")
public List<Object> buscaUsuarioRedis(){
return redisRepository.getAllUsers();
}
@PostMapping("user")
public String criaUsuario(@RequestBody UserRedis usuarioRedis){
UserRedis userRedisExiste = redisRepository.getUser(usuarioRedis.getId());
if(userRedisExiste == null) {
return "Nao ha este usuario no banco de memoria";
}
logger.info("Salvando usuario " + usuarioRedis.getName());
UserPostgres usuario = usuarioRedis.converte();
String message = userService.save(usuario);
logger.info(message);
if(message == "Salvo com sucesso") {
redisRepository.deleteItem(usuarioRedis.getId());
}
return message;
}
@DeleteMapping("user/{id}")
public void deletaUsuarioRedis(@PathVariable String id){
redisRepository.deleteItem(id);
}
}
Bora rodar nossas 3 APIs e realizar a chamada via postman para testarmos o funcionamento.
Requisição para salvar um usuario
Logs da nossa primeira aplicação (ponte acesso), mensagem produzida no tópico kafka
Logs da nossa segunda aplicação (orquestrador), mensagem consumida via tópico Kafka, produzida na fila do broker activemq e salva no elasticsearch.
Podemos visualizar o usuário salvo no elasticsearch.
Logs da nossa terceira aplicação (consumer da fila), mensagem consumida e usuario salvo no redis.
Usuario salvo no Redis
Para finalizar vamos agora realizar nossa requisição via postman dos dados do usuário salvo no Redis para salvar no postgres.
Usuário salvo em nosso banco de dados Postgres
Usuário deletado do nosso banco de memorias Redis
E isso ai galera finalizando aqui a parte de backend do nosso sistema, no próximo post iremos criar o front para testarmos a logica de aceitar ou não cadastro de um usuário.
Abaixo segue o link do github onde a aplicação esta armazenada para que possam conferir como a mesma ficou.
Posted on March 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.