Microservices, Docker e Tecnologias de Mensageria parte 4
Tags: redis, microservices, activemq, postgres
Published: 2022-03-01 03:22:06

Ferramentas necessárias:
* [Configuração ambiente da parte 2] (https://dev.to/2020nani/microservices-docker-e-tecnologias-de-mensageria-parte-2-325e)
* [Java] (https://www.oracle.com/java/technologies/downloads/)
* [Docker](https://docs.docker.com/desktop/windows/install/#:~:text=%20Install%20Docker%20Desktop%20on%20Windows%20%F0%9F%94%97%20,to%20authorize%20the%20installer%20and%20proceed...%20More%20)

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](https://start.spring.io/) com as seguintes dependências necessárias
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9z9edterdi71gowix6v.png)
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**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52arzkwh2f3mvx3urssa.png)

Logs da nossa primeira aplicação (ponte acesso), mensagem produzida no tópico kafka
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfwdm4p9zazksmv2mx0d.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjshtctu1d68q4mkn41j.png)
Podemos visualizar o usuário salvo no elasticsearch.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ussetu9bbtqb8w7kl4fh.png)
 
Logs da nossa terceira aplicação (consumer da fila), mensagem consumida e usuario salvo no redis.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/158shpnqmp630jmha7ho.png)
Usuario salvo no Redis
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipz9qjq3lb1wkdl8aef7.png)

Para finalizar vamos agora realizar nossa requisição via postman dos dados do usuário salvo no Redis para salvar no postgres.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyvkxyamcvp3i6qwock6.png)
Usuário salvo em nosso banco de dados Postgres
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5z6xs1t5v50oh7hhcav.png)
Usuário deletado do nosso banco de memorias Redis
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gstkfyr7ej1mutwkygc6.png)
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](https://github.com/2020nani/testeMicroservi-os/tree/master/consumer)

[Repositorio com todas as aplicações](https://github.com/2020nani/testeMicroservi-os)

[Parte 5] (https://dev.to/2020nani/microservices-docker-e-tecnologias-de-mensageria-parte-5-15dc)

[linkedin](https://www.linkedin.com/in/nanialmeida/)
[github](https://github.com/2020nani)