Book Image

Spring 5.0 By Example

By : Claudio Eduardo de Oliveira
Book Image

Spring 5.0 By Example

By: Claudio Eduardo de Oliveira

Overview of this book

With growing demands, organizations are looking for systems that are robust and scalable. Therefore, the Spring Framework has become the most popular framework for Java development. It not only simplifies software development but also improves developer productivity. This book covers effective ways to develop robust applications in Java using Spring. The book has three parts, where each one covers the building of a comprehensive project in Java and Spring. In the first part, you will construct a CMS Portal using Spring's support for building REST APIs. You will also learn to integrate these APIs with AngularJS and later develop this application in a reactive fashion using Project Reactor, Spring WebFlux, and Spring Data. In the second part, you’ll understand how to build a messaging application, which will consume the Twitter API and perform filtering and transformations. Here, you will also learn about server-sent events and explore Spring’s support for Kotlin, which makes application development quick and efficient. In the last part, you will build a real microservice application using the most important techniques and patterns such as service discovery, circuit breakers, security, data streams, monitoring, and a lot more from this architectural style. By the end of the book, you will be confident about using Spring to build your applications.
Table of Contents (17 chapters)
Title Page
Copyright and Credits
Dedication
Packt Upsell
Contributors
Preface
Index

Creating the Mail microservice


Now, we will create our Mail microservice. The name is self-explanatory, this component will be responsible for sending emails. We will not configure an SMTP (Simple Mail Transfer Protocol) server, we will use SendGrid.

SendGrid is an SaaS (Software as a Service) service for emails, we will use this service to send emails to our Airline Ticket System. There are some triggers to send email, for example, when the user creates a booking and when the payment is accepted.

Our Mail microservice will listen to a queue. Then the integration will be done using the message broker. We choose this strategy because we do not need the feature that enables us to answer synchronously. Another essential characteristic is the retry policy when the communication is broken. This behavior can be done easily using the message strategy.

We are using RabbitMQ as a message broker. For this project, we will use RabbitMQ Reactor, which is a reactive implementation of RabbitMQ Java client.

Creating the SendGrid account

Before we start to code, we need to create a SendGrid account. We will use the trial account which is enough for our tests. Go to the SendGrid portal (https://sendgrid.com/) and click on the Try for Free button.

Fill in the required information and click on the Create Account button.

In the main page, on the left side, click on Settings, then go to the API Key section, follow the image shown here:

Then, we can click on the Create API Key button at the top-right corner. The page should look like this:

Fillin the API Key information and choose Full Access. After that the API Key will appear on your screen. Take a note of it in a safe place, as we will use it as an environment variable soon.

Goob job, our SendGrid account is ready to use, now we can code our Mail microservice.

Let's do it in the next section.

Creating the Mail microservice project

As we did in Chapter 8, Circuit Breakers and Security, we will take a look at essential project parts. We will be using Spring Initializr, as we have several times in the previous chapters.

Adding RabbitMQ dependencies

Let's add the RabbitMQ required dependencies. The following dependencies should be added:

<dependency>
  <groupId>io.projectreactor.rabbitmq</groupId>
  <artifactId>reactor-rabbitmq</artifactId>
  <version>1.0.0.M1</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

The first one is about the reactive implementation for RabbitMQ and the second one is the starter AMQP, which will set up some configurations automatically.

Configuring some RabbitMQ stuff

We want to configure some RabbitMQ exchanges, queues, and bindings. It can be done using the RabbitMQ client library. We will configure our required infrastructure for the Mail microservice.

Our configuration class should look like this:

package springfive.airline.mailservice.infra.rabbitmq;

// imports are omitted

@Configuration
public class RabbitMQConfiguration {

  private final String pass;

  private final String user;

  private final String host;

  private final Integer port;

  private final String mailQueue;

  public RabbitMQConfiguration(@Value("${spring.rabbitmq.password}") String pass,
      @Value("${spring.rabbitmq.username}") String user,
      @Value("${spring.rabbitmq.host}") String host,
      @Value("${spring.rabbitmq.port}") Integer port,
      @Value("${mail.queue}") String mailQueue) {
    this.pass = pass;
    this.user = user;
    this.host = host;
    this.port = port;
    this.mailQueue = mailQueue;
  }

  @Bean("springConnectionFactory")
  public ConnectionFactory connectionFactory() {
    CachingConnectionFactory factory = new CachingConnectionFactory();
    factory.setUsername(this.user);
    factory.setPassword(this.pass);
    factory.setHost(this.host);
    factory.setPort(this.port);
    return factory;
  }

  @Bean
  public AmqpAdmin amqpAdmin(@Qualifier("springConnectionFactory") ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
  }

  @Bean
  public TopicExchange emailExchange() {
    return new TopicExchange("email", true, false);
  }

  @Bean
  public Queue mailQueue() {
    return new Queue(this.mailQueue, true, false, false);
  }

  @Bean
  public Binding mailExchangeBinding(Queue mailQueue) {
    return BindingBuilder.bind(mailQueue).to(emailExchange()).with("*");
  }

  @Bean
  public Receiver receiver() {
    val options = new ReceiverOptions();
    com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
    connectionFactory.setUsername(this.user);
    connectionFactory.setPassword(this.pass);
    connectionFactory.setPort(this.port);
    connectionFactory.setHost(this.host);
    options.connectionFactory(connectionFactory);
    return ReactorRabbitMq.createReceiver(options);
  }

}

There is interesting stuff here, but all of it is about infrastructure in RabbitMQ. It is important because when our application is in bootstrapping time, it means our application is preparing to run. This code will be executed and create the necessary queues, exchanges, and bindings. Some configurations are provided by the application.yaml file, look at the constructor.

Modeling a Mail message

Our Mail service is abstract and can be used for different purposes, so we will create a simple class to represent a mail message in our system. Our Mail class should look like this:

package springfive.airline.mailservice.domain;

import lombok.Data;

@Data
public class Mail {

  String from;

  String to;

  String subject;

  String message;

}

Easy, this class represents an abstract message on our system.

The MailSender class

As we can expect, we will integrate with the SendGrid services through the REST APIs. In our case, we will use the reactive WebClient provided by Spring WebFlux.

Now, we will use the SendGrid API Key created in the previous section. Our MailSender class should look like this:

package springfive.airline.mailservice.domain.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import springfive.airline.mailservice.domain.Mail;
import springfive.airline.mailservice.domain.service.data.SendgridMail;

@Service
public class MailSender {

  private final String apiKey;

  private final String url;

  private final WebClient webClient;

  public MailSender(@Value("${sendgrid.apikey}") String apiKey,
      @Value("${sendgrid.url}") String url,
      WebClient webClient) {
    this.apiKey = apiKey;
    this.webClient = webClient;
    this.url = url;
  }

  public Flux<Void> send(Mail mail){
    final BodyInserter<SendgridMail, ReactiveHttpOutputMessage> body = BodyInserters
      .fromObject(SendgridMail.builder().content(mail.getMessage()).from(mail.getFrom()).to(mail.getTo()).subject(mail.getSubject()).build());
    return this.webClient.mutate().baseUrl(this.url).build().post()
        .uri("/v3/mail/send")
        .body(body)
        .header("Authorization","Bearer " + this.apiKey)
        .header("Content-Type","application/json")
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, clientResponse ->
            Mono.error(new RuntimeException("Error on send email"))
        ).bodyToFlux(Void.class);
  }

}

We received the configurations in the constructor, that is, the sendgrid.apikey and sendgrid.url. They will be configured soon. In the send() method, there are some interesting constructions. Look at BodyInserters.fromObject(): it allows us to send a JSON object in the HTTP body. In our case, we will create a SendGrid mail object.

In the onStatus() function, we can pass a predicate to handle the HTTP errors family. In our case, we are interested in the 4xx error family.

This class will process sending the mail messages, but it is necessary to listen to the RabbbitMQ queue, which we will do in the next section.

Creating the RabbitMQ queue listener

Let's create our MailQueueConsumer class, which will listen to the RabbitMQ queue. The class should look like this:

package springfive.airline.mailservice.domain.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.rabbitmq.Receiver;
import springfive.airline.mailservice.domain.Mail;

@Service
@Slf4j
public class MailQueueConsumer {

  private final MailSender mailSender;

  private final String mailQueue;

  private final Receiver receiver;

  private final ObjectMapper mapper;

  public MailQueueConsumer(MailSender mailSender, @Value("${mail.queue}") String mailQueue,
      Receiver receiver, ObjectMapper mapper) {
    this.mailSender = mailSender;
    this.mailQueue = mailQueue;
    this.receiver = receiver;
    this.mapper = mapper;
  }

  @PostConstruct
  public void startConsume() {
    this.receiver.consumeAutoAck(this.mailQueue).subscribe(message -> {
      try {
        val mail = this.mapper.readValue(new String(message.getBody()), Mail.class);
        this.mailSender.send(mail).subscribe(data ->{
          log.info("Mail sent successfully");
        });
      } catch (IOException e) {
        throw new RuntimeException("error on deserialize object");
      }
    });
  }

}

The method annotated with @PostConstruct will be invoked after MailQueueConsumer is ready, which will mean that the injections are processed. Then Receiver will start to process the messages.

Running the Mail microservice

Now, we will run our Mail microservice. Find the MailServiceApplication class, the main class of our project. The main class should look like this:

package springfive.airline.mailservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableHystrix
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class MailServiceApplication {

  public static void main(String[] args) {
    SpringApplication.run(MailServiceApplication.class, args);
  }

}

It is a standard Spring Boot Application.

We can run the application in IDE or via the Java command line.

Run it!

Note

We need to pass ${SENDGRID_APIKEY} and ${SENDGRID_URL} as environment variables. If you are running the application with the Java command line, the -D option allows us to pass environment variables. If you are using the IDE, you can configure in the Run/Debug Configurations.