Microservices, Docker e Tecnologias de Mensageria parte 4

2020nani

Hernani Almeida

Posted on March 1, 2022

Microservices, Docker e Tecnologias de Mensageria parte 4

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
Image description
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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);

    }

}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }

}
Enter fullscreen mode Exit fullscreen mode

Bora rodar nossas 3 APIs e realizar a chamada via postman para testarmos o funcionamento.
Requisição para salvar um usuario
Image description

Logs da nossa primeira aplicação (ponte acesso), mensagem produzida no tópico kafka
Image description

Logs da nossa segunda aplicação (orquestrador), mensagem consumida via tópico Kafka, produzida na fila do broker activemq e salva no elasticsearch.
Image description
Podemos visualizar o usuário salvo no elasticsearch.
Image description

Logs da nossa terceira aplicação (consumer da fila), mensagem consumida e usuario salvo no redis.
Image description
Usuario salvo no Redis
Image description

Para finalizar vamos agora realizar nossa requisição via postman dos dados do usuário salvo no Redis para salvar no postgres.
Image description
Usuário salvo em nosso banco de dados Postgres
Image description
Usuário deletado do nosso banco de memorias Redis
Image description
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.

API Consumer Fila

Repositorio com todas as aplicações

Parte 5

linkedin
github

💖 💪 🙅 🚩
2020nani
Hernani Almeida

Posted on March 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related