Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Microservices with Spring Boot 3 and Spring Cloud, Third Edition - Third Edition
Microservices with Spring Boot 3 and Spring Cloud, Third Edition - Third Edition

Microservices with Spring Boot 3 and Spring Cloud, Third Edition: Build resilient and scalable microservices using Spring Cloud, Istio, and Kubernetes, Third Edition

By Magnus Larsson
£26.99 £17.99
Book Aug 2023 706 pages 3rd Edition
eBook
£26.99 £17.99
Print
£33.99
Subscription
£13.99 Monthly
eBook
£26.99 £17.99
Print
£33.99
Subscription
£13.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Aug 31, 2023
Length 706 pages
Edition : 3rd Edition
Language : English
ISBN-13 : 9781805128694
Category :
Concepts :
Table of content icon View table of contents Preview book icon Preview Book

Microservices with Spring Boot 3 and Spring Cloud, Third Edition - Third Edition

Introduction to Spring Boot

In this chapter, we will be introduced to how to build a set of cooperating microservices using Spring Boot, focusing on how to develop functionality that delivers business value. The challenges with microservices that we pointed out in the previous chapter will be considered only to some degree, but they will be addressed to their full extent in later chapters.

We will develop microservices that contain business logic based on plain Spring Beans and expose REST APIs using Spring WebFlux. The APIs will be documented based on the OpenAPI Specification using springdoc-openapi. To make the data processed by the microservices persistent, we will use Spring Data to store data in both SQL and NoSQL databases.

Since Spring Boot v2.0 was released in March 2018, it has become much easier to develop reactive microservices, including non-blocking synchronous REST APIs. To develop message-based asynchronous services, we will use Spring Cloud Stream. Refer to Chapter 1, Introduction to Microservices, the Reactive microservices section, for more information.

In November 2022, Spring Boot 3.0 was released. It is based on Spring Framework 6.0 and Jakarta EE 9, also being compatible with Jakarta EE 10. Java 17, the current long-term support (LTS) release, is required as the minimum Java version.

Finally, we will use Docker to run our microservices as containers. This will allow us to start and stop our microservice landscape, including database servers and a message broker, with a single command.

That’s a lot of technologies and frameworks, so let’s go through each of them briefly to see what they are about!

In this chapter, we will introduce the following open source projects:

  • Spring Boot

(This section also includes an overview of what’s new in v3.0 and on how to migrate v2 applications.)

  • Spring WebFlux
  • springdoc-openapi
  • Spring Data
  • Spring Cloud Stream
  • Docker

More details about each product will be provided in upcoming chapters.

Technical requirements

This chapter does not contain any source code that can be downloaded, nor does it require any tools to be installed.

Spring Boot

Spring Boot, and the Spring Framework that Spring Boot is based on, is a great framework for developing microservices in Java.

When the Spring Framework v1.0 was released back in 2004, one of its main goals was to address the overly complex J2EE standard (short for Java 2 Platform, Enterprise Edition) with its infamous and heavyweight deployment descriptors. The Spring Framework provided a much more lightweight development model based on the concept of dependency injection. The Spring Framework also used far more lightweight XML configuration files compared to the deployment descriptors in J2EE.

To make things even worse with the J2EE standard, the heavyweight deployment descriptors actually came in two types:

  • Standard deployment descriptors, describing the configuration in a standardized way
  • Vendor-specific deployment descriptors, mapping the configuration to vendor-specific features in the vendor’s application server

In 2006, J2EE was renamed Java EE, short for Java Platform, Enterprise Edition. In 2017, Oracle submitted Java EE to the Eclipse Foundation. In February 2018, Java EE was renamed Jakarta EE. The new name, Jakarta EE, also affects the names of the Java packages defined by the standard, requiring developers to perform package renaming when upgrading to Jakarta EE, as described in the Migrating a Spring Boot 2 application section. Over the years, while the Spring Framework gained increasing popularity, the functionality in the Spring Framework grew significantly. Slowly, the burden of setting up a Spring application using the no-longer-so-lightweight XML configuration file became a problem.

In 2014, Spring Boot v1.0 was released, addressing these problems!

Convention over configuration and fat JAR files

Spring Boot targets the fast development of production-ready Spring applications by being strongly opinionated about how to set up both core modules from the Spring Framework and third-party products, such as libraries that are used for logging or connecting to a database. Spring Boot does that by applying a number of conventions by default, minimizing the need for configuration. Whenever required, each convention can be overridden by writing some configuration, case by case. This design pattern is known as convention over configuration and minimizes the need for initial configuration.

Configuration, when required, is, in my opinion, written best using Java and annotations. The good old XML-based configuration files can still be used, although they are significantly smaller than before Spring Boot was introduced.

Added to the usage of convention over configuration, Spring Boot also favors a runtime model based on a standalone JAR file, also known as a fat JAR file. Before Spring Boot, the most common way to run a Spring application was to deploy it as a WAR file on a Java EE web server, such as Apache Tomcat. WAR file deployment is still supported by Spring Boot.

A fat JAR file contains not only the classes and resource files of the application itself but also all the JAR files the application depends on. This means that the fat JAR file is the only JAR file required to run the application; that is, we only need to transfer one JAR file to an environment where we want to run the application instead of transferring the application’s JAR file along with all the JAR files the application depends on.

Starting a fat JAR requires no separately installed Java EE web server, such as Apache Tomcat. Instead, it can be started with a simple command such as java -jar app.jar, making it a perfect choice for running in a Docker container! If the Spring Boot application, for example, uses HTTP to expose a REST API, it will also contain an embedded web server.

Code examples for setting up a Spring Boot application

To better understand what this means, let’s look at some source code examples.

We will only look at some small fragments of code here to point out the main features. For a fully working example, you’ll have to wait until the next chapter!

The magic @SpringBootApplication annotation

The convention-based autoconfiguration mechanism can be initiated by annotating the application class (that is, the class that contains the static main method) with the @SpringBootApplication annotation. The following code shows this:

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

The following functionality will be provided by this annotation:

  • It enables component scanning, that is, looking for Spring components and configuration classes in the package of the application class and all its sub-packages.
  • The application class itself becomes a configuration class.
  • It enables autoconfiguration, where Spring Boot looks for JAR files in the classpath that it can configure automatically. For example, if you have Tomcat in the classpath, Spring Boot will automatically configure Tomcat as an embedded web server.

Component scanning

Let’s assume we have the following Spring component in the package of the application class (or in one of its sub-packages):

@Component
public class MyComponentImpl implements MyComponent { ...

Another component in the application can get this component automatically injected, also known as auto-wired, using the @Autowired annotation:

public class AnotherComponent {
  private final MyComponent myComponent;
  @Autowired
  public AnotherComponent(MyComponent myComponent) {
    this.myComponent = myComponent;
  }

I prefer using constructor injection (over field and setter injection) to keep the state in my components immutable. An immutable state is important if you want to be able to run the component in a multithreaded runtime environment.

If we want to use components that are declared in a package outside the application’s package, for example, a utility component shared by multiple Spring Boot applications, we can complement the @SpringBootApplication annotation in the application class with a @ComponentScan annotation:

package se.magnus.myapp;
@SpringBootApplication
@ComponentScan({"se.magnus.myapp","se.magnus.util" })
public class MyApplication {

We can now auto-wire components from the se.magnus.util package in the application code, for example, a utility component named MyUtility, as follows:

package se.magnus.util;
@Component
public class MyUtility { ...

This utility component can be auto-wired in an application component like so:

package se.magnus.myapp.services;
public class AnotherComponent {
 private final MyUtility myUtility;
 @Autowired
 public AnotherComponent(MyUtility myUtility) {
   this.myUtility = myUtility;
 }

Java-based configuration

If we want to override Spring Boot’s default configuration or we want to add our own configuration, we can simply annotate a class with @Configuration and it will be picked up by the component scanning mechanism we described previously.

For example, if we want to set up a filter in the processing of HTTP requests (handled by Spring WebFlux, which is described in the following section) that writes a log message at the beginning and the end of the processing, we can configure a log filter, as follows:

@Configuration
public class SubscriberApplication {
  @Bean
  public Filter logFilter() {
    CommonsRequestLoggingFilter filter = new 
        CommonsRequestLoggingFilter();
    filter.setIncludeQueryString(true);
    filter.setIncludePayload(true);
    filter.setMaxPayloadLength(5120);
    return filter;
  }

We can also place the configuration directly in the application class since the @SpringBootApplication annotation implies the @Configuration annotation.

That’s all for now about Spring Boot, but before moving to the next component, let’s see what is new in Spring Boot 3.0 and how to migrate a Spring Boot 2 application.

What’s new in Spring Boot 3.0

For the scope of this book, the most important new items in Spring Boot 3.0 are the following:

  • Observability

    Spring Boot 3.0 comes with improved support for observability, adding built-in support for distributed tracing to the already existing support for metrics and logging in previous Spring Boot releases. The new distributed tracing support is based on a new Observability API in Spring Framework v6.0 and a new module named Micrometer Tracing. Micrometer Tracing is based on Spring Cloud Sleuth, which is now deprecated. Chapter 14, Understand Distributed Tracing, covers how to use the new support for observability and distributed tracing.

  • Native compilation

    Spring Boot 3.0 also comes with support for compiling Spring Boot applications to native images, which are standalone executable files. A native-compiled Spring Boot application starts significantly faster and consumes less memory. Chapter 23, Native-Compiled Java Microservices, describes how to native compile microservices based on Spring Boot.

  • Virtual threads

    Finally, Spring Boot 3.0 comes with support for lightweight threads called virtual threads from the OpenJDK Project Loom. Virtual threads are expected to simplify the programming model for developing reactive non-blocking microservices, for example, compared to the programming model used in Project Reactor and various Spring components. Virtual threads are currently only available as a preview in Java 19. They also currently lack support for composability features, for example, required to build microservices that concurrently aggregate information from other microservices. Therefore, virtual threads will not be covered in this book. Chapter 7, Developing Reactive Microservices, covers how virtual threads can be implemented using Project Reactor and Spring WebFlux.

Migrating a Spring Boot 2 application

If you already have applications based on Spring Boot 2, you might be interested in understanding what it takes to migrate to Spring Boot 3.0. Here is a list of actions you need to take:

  1. Pivotal recommends first upgrading Spring Boot 2 applications to the latest v2.7.x release since their migration guide assumes you are on v2.7.
  2. Ensure you have Java 17 or later installed, both in your development and runtime environments. If your Spring Boot applications are deployed as Docker containers, you need to ensure that your company approves the usage of Docker images based on Java 17 or newer releases.
  3. Remove calls to deprecated methods in Spring Boot 2.x. All deprecated methods are removed in Spring Boot 3.0, so you must ensure that your application does not call any of these methods. To see exactly where calls are being made in your application, you can enable the lint:deprecation flag in the Java compiler using (assuming the use of Gradle):
    tasks.withType(JavaCompile) {
        options.compilerArgs += ['-Xlint:deprecation']
    }
    
  4. Rename all imports of javax packages that are now part of Jakarta EE to jakarta.
  5. For libraries that are not managed by Spring, you need to ensure that you are using versions that are Jakarta compliant, that is, using jakarta packages.
  6. For breaking changes and other important migration information, read through:

    https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide

    https://docs.spring.io/spring-security/reference/migration/index.html

  1. Ensure that you have end-to-end black-box tests that verify the functionality of your application. Run these tests before and after the migration to ensure that the application’s functionality has not been affected by the migration.

When migrating the source code of the previous edition of this book to Spring Boot 3.0, the most time-consuming part was figuring out how to handle breaking changes in the Spring Security configuration; see Chapter 11, Securing Access to APIs, for details. As an example, the following configuration of the authorization server in the previous edition needed to be updated:

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
  http
    .authorizeRequests(authorizeRequests -> authorizeRequests
      .antMatchers("/actuator/**").permitAll()

This configuration looks like the following with Spring Boot 3.0:

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
  http
    .authorizeHttpRequests(authorizeRequests -> authorizeRequests
      .requestMatchers("/actuator/**").permitAll()

The end-to-end test script, test-em-all.bash, that comes with each chapter turned out to be indispensable in verifying that the functionality was unaffected after the migration of each chapter.

Now that we have learned about Spring Boot, let’s talk about Spring WebFlux.

Spring WebFlux

Spring Boot 3.0 is based on the Spring Framework 6.0, which has built-in support for developing reactive applications. The Spring Framework uses Project Reactor as the base implementation of its reactive support and also comes with a new web framework, Spring WebFlux, which supports the development of reactive, that is, non-blocking, HTTP clients and services.

Spring WebFlux supports two different programming models:

  • An annotation-based imperative style, similar to the already existing web framework, Spring Web MVC, but with support for reactive services
  • A new function-oriented model based on routers and handlers

In this book, we will use the annotation-based imperative style to demonstrate how easy it is to move REST services from Spring Web MVC to Spring WebFlux and then start to refactor the services so that they become fully reactive.

Spring WebFlux also provides a fully reactive HTTP client, WebClient, as a complement to the existing RestTemplate client.

Spring WebFlux supports running on a servlet container based on the Jakarta Servlet specification v5.0 or higher, such as Apache Tomcat, but also supports reactive non-servlet-based embedded web servers such as Netty (https://netty.io/).

The Servlet specification is a specification in the Java EE platform that standardizes how to develop Java applications that communicate using web protocols such as HTTP.

Code examples of setting up a REST service

Before we can create a REST service based on Spring WebFlux, we need to add Spring WebFlux (and the dependencies that Spring WebFlux requires) to the classpath for Spring Boot to be detected and configured during startup. Spring Boot provides a large number of convenient starter dependencies that bring in a specific feature, together with the dependencies each feature normally requires. So, let’s use the starter dependency for Spring WebFlux and then see what a simple REST service looks like!

Starter dependencies

In this book, we will use Gradle as our build tool, so the Spring WebFlux starter dependency will be added to the build.gradle file. It looks like this:

implementation('org.springframework.boot:spring-boot-starter-webflux')

You might be wondering why we don’t specify a version number. We will talk about that when we look at a complete example in Chapter 3, Creating a Set of Cooperating Microservices!

When the microservice is started up, Spring Boot will detect Spring WebFlux on the classpath and configure it, as well as other things such as starting up an embedded web server. Spring WebFlux uses Netty by default, which we can see from the log output:

2023-03-09 15:23:43.592 INFO 17429 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080

If we want to switch from Netty to Tomcat as our embedded web server, we can override the default configuration by excluding Netty from the starter dependency and adding the starter dependency for Tomcat:

implementation('org.springframework.boot:spring-boot-starter-webflux') 
{
 exclude group: 'org.springframework.boot', module: 'spring-boot-
 starter-reactor-netty'
}
implementation('org.springframework.boot:spring-boot-starter-tomcat')

After restarting the microservice, we can see that Spring Boot picked Tomcat instead:

2023-03-09 18:23:44.182 INFO 17648 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)

Property files

As you can see from the preceding examples, the web server is started up using port 8080. If you want to change the port, you can override the default value using a property file. Spring Boot application property files can either be a .properties file or a YAML file. By default, they are named application.properties and application.yml, respectively.

In this book, we will use YAML files so that the HTTP port used by the embedded web server can be changed to, for example, 7001. By doing this, we can avoid port collisions with other microservices running on the same server. To do this, we can add the following line to the application.yml file:

server.port: 7001

When we begin to develop our microservices as containers in Chapter 4, Deploying Our Microservices Using Docker, port collisions will no longer be a problem. Each container has its own hostname and port range, so all microservices can use, for example, port 8080 without colliding with each other.

Sample RestController

Now, with Spring WebFlux and an embedded web server of our choice in place, we can write a REST service in the same way as when using Spring MVC, that is, as a RestController:

@RestController
public class MyRestService {
  @GetMapping(value = "/my-resource", produces = "application/json")
  List<Resource> listResources() {
    …
  }

The @GetMapping annotation on the listResources() method will map the Java method to an HTTP GET API on the host:8080/myResource URL. The return value of the List<Resource> type will be converted into JSON.

Now that we’ve talked about Spring WebFlux, let’s see how we can document the APIs we develop using Spring WebFlux.

springdoc-openapi

One very important aspect of developing APIs, for example, RESTful services, is how to document them so that they are easy to use. The Swagger specification from SmartBear Software is one of the most widely used ways of documenting RESTful services. Many leading API gateways have native support for exposing the documentation of RESTful services using the Swagger specification.

In 2015, SmartBear Software donated the Swagger specification to the Linux Foundation under the OpenAPI Initiative and created the OpenAPI Specification. The name Swagger is still used for the tooling provided by SmartBear Software.

springdoc-openapi is an open source project, separate from the Spring Framework, that can create OpenAPI-based API documentation at runtime. It does so by examining the application, for example, inspecting WebFlux and Swagger-based annotations.

We will look at full source code examples in upcoming chapters, but for now, the following condensed screenshot (removed parts are marked with “”) of a sample API documentation will do:

Graphical user interface, application, Teams  Description automatically generated

Figure 2.1: Sample API documentation visualized using Swagger UI

Note the big Execute button, which can be used to actually try out the API, not just read its documentation!

springdoc-openapi helps us to document the APIs exposed by our microservices. Now, let’s move on to Spring Data.

Spring Data

Spring Data comes with a common programming model for persisting data in various types of database engines, ranging from traditional relational databases (SQL databases) to various types of NoSQL database engines, such as document databases (for example, MongoDB), key-value databases (for example, Redis), and graph databases (for example, Neo4j).

The Spring Data project is divided into several subprojects, and in this book, we will use Spring Data subprojects for MongoDB and JPA that have been mapped to a MySQL database.

JPA stands for Jakarta Persistence API and is a Java specification about how to handle relational data. Please go to https://jakarta.ee/specifications/persistence/ for the latest specifications. Jakarta EE 9 is based on Jakarta Persistence 3.0.

The two core concepts of the programming model in Spring Data are entities and repositories. Entities and repositories generalize how data is stored and accessed from the various types of databases. They provide a common abstraction but still support adding database-specific behavior to the entities and repositories. These two core concepts are briefly explained together with some illustrative code examples as we proceed through this chapter. Remember that more details will be provided in the upcoming chapters!

Even though Spring Data provides a common programming model for different types of databases, this doesn’t mean that you will be able to write portable source code. For example, switching the database technology from a SQL database to a NoSQL database will, in general, not be possible without some changes in the source code!

Entity

An entity describes the data that will be stored by Spring Data. Entity classes are, in general, annotated with a mix of generic Spring Data annotations and annotations that are specific to each database technology.

For example, an entity that will be stored in a relational database can be annotated with JPA annotations such as the following:

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
@Entity
@IdClass(ReviewEntityPK.class)
@Table(name = "review")
public class ReviewEntity {
 @Id private int productId;
 @Id private int reviewId;
 private String author;
 private String subject;
 private String content;

If an entity is to be stored in a MongoDB database, annotations from the Spring Data MongoDB subproject can be used together with generic Spring Data annotations. For example, consider the following code:

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.mapping.Document;
@Document
public class RecommendationEntity {
    @Id
    private String id;
    @Version
    private int version;
    private int productId;
    private int recommendationId;
    private String author;
    private int rate;
    private String content;

The @Id and @Version annotations are generic annotations, while the @Document annotation is specific to the Spring Data MongoDB subproject.

This can be revealed by studying the import statements; the import statements that contain mongodb come from the Spring Data MongoDB subproject.

Repositories

Repositories are used to store and access data from different types of databases. In its most basic form, a repository can be declared as a Java interface, and Spring Data will generate its implementation on the fly using opinionated conventions. These conventions can be overridden and/or complemented by additional configuration and, if required, some Java code.

Spring Data also comes with some base Java interfaces, for example, CrudRepository, to make the definition of a repository even simpler. The base interface, CrudRepository, provides us with standard methods for create, read, update, and delete operations.

To specify a repository for handling the JPA entity ReviewEntity, we only need to declare the following:

import org.springframework.data.repository.CrudRepository;
public interface ReviewRepository extends 
  CrudRepository<ReviewEntity, ReviewEntityPK> {
  
  Collection<ReviewEntity> findByProductId(int productId);
}

In this example we use a class, ReviewEntityPK, to describe a composite primary key. It looks as follows:

public class ReviewEntityPK implements Serializable {
    public int productId;
    public int reviewId;
}

We have also added an extra method, findByProductId, which allows us to look up Review entities based on productid – a field that is part of the primary key. The naming of the method follows a naming convention defined by Spring Data that allows Spring Data to generate the implementation of this method on the fly as well.

If we want to use the repository, we can simply inject it and then start to use it, for example:

private final ReviewRepository repository;
@Autowired
public ReviewService(ReviewRepository repository) {
 this.repository = repository;
}
public void someMethod() {
  repository.save(entity);
  repository.delete(entity);
  repository.findByProductId(productId);

Added to the CrudRepository interface, Spring Data also provides a reactive base interface, ReactiveCrudRepository, which enables reactive repositories. The methods in this interface do not return objects or collections of objects; instead, they return Mono and Flux objects. Mono and Flux objects are, as we will see in Chapter 7, Developing Reactive Microservices, reactive streams that are capable of returning either 0...1 or 0...m entities as they become available on the stream.

The reactive-based interface can only be used by Spring Data subprojects that support reactive database drivers; that is, they are based on non-blocking I/O. The Spring Data MongoDB subproject supports reactive repositories, while Spring Data JPA does not.

Specifying a reactive repository for handling the MongoDB entity, RecommendationEntity, as described previously, might look something like the following:

import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
public interface RecommendationRepository extends ReactiveCrudRepository<RecommendationEntity, String> {
    Flux<RecommendationEntity> findByProductId(int productId);
}

This concludes the section on Spring Data. Now let’s see how we can use Spring Cloud Stream to develop message-based asynchronous services.

Spring Cloud Stream

We will not focus on Spring Cloud in this part; we will do that in Part 2 of the book, from Chapter 8, Introduction to Spring Cloud, to Chapter 14, Understanding Distributed Tracing. However, we will bring in one of the modules that’s part of Spring Cloud: Spring Cloud Stream. Spring Cloud Stream provides a streaming abstraction over messaging, based on the publish and subscribe integration pattern. Spring Cloud Stream currently comes with built-in support for Apache Kafka and RabbitMQ. A number of separate projects exist that provide integration with other popular messaging systems. See https://github.com/spring-cloud?q=binder for more details.

The core concepts in Spring Cloud Stream are as follows:

  • Message: A data structure that’s used to describe data sent to and received from a messaging system.
  • Publisher: Sends messages to the messaging system, also known as a Supplier.
  • Subscriber: Receives messages from the messaging system, also known as a Consumer.
  • Destination: Used to communicate with the messaging system. Publishers use output destinations and subscribers use input destinations. Destinations are mapped by the specific binders to queues and topics in the underlying messaging system.
  • Binder: A binder provides the actual integration with a specific messaging system, similar to what a JDBC driver does for a specific type of database.

The actual messaging system to be used is determined at runtime, depending on what is found on the classpath. Spring Cloud Stream comes with opinionated conventions on how to handle messaging. These conventions can be overridden by specifying a configuration for messaging features such as consumer groups, partitioning, persistence, durability, and error handling; for example, retries and dead letter queue handling.

Code examples for sending and receiving messages

To better understand how all this fits together, let’s look at some source code examples.

Spring Cloud Stream comes with two programming models: one older and nowadays deprecated model based on the use of annotations (for example, @EnableBinding, @Output, and @StreamListener) and one newer model based on writing functions. In this book, we will use functional implementations.

To implement a publisher, we only need to implement the java.util.function.Supplier functional interface as a Spring Bean. For example, the following is a publisher that publishes messages as a String:

@Bean
public Supplier<String> myPublisher() {
   return () -> new Date().toString();
}

A subscriber is implemented as a Spring Bean implementing the java.util.function.Consumer functional interface. For example, the following is a subscriber that consumes messages as Strings:

@Bean
public Consumer<String> mySubscriber() {
   return s -> System.out.println("ML RECEIVED: " + s);
}

It is also possible to define a Spring Bean that processes messages, meaning that it both consumes and publishes messages. This can be done by implementing the java.util.function.Function functional interface. For example, a Spring Bean that consumes incoming messages and publishes a new message after some processing (both messages are Strings in this example):

@Bean
public Function<String, String> myProcessor() {
   return s -> "ML PROCESSED: " + s;
}

To make Spring Cloud Stream aware of these functions, we need to declare them using the spring.cloud.function.definition configuration property. For example, for the three functions defined previously, this would look as follows:

spring.cloud.function:
  definition: myPublisher;myProcessor;mySubscriber

Finally, we need to tell Spring Cloud Stream what destination to use for each function. To connect our three functions so that our processor consumes messages from our publisher and our subscriber consumes messages from the processor, we can supply the following configuration:

spring.cloud.stream.bindings:
  myPublisher-out-0:
    destination: myProcessor-in
  myProcessor-in-0:
    destination: myProcessor-in
  myProcessor-out-0:
    destination: myProcessor-out
  mySubscriber-in-0:
    destination: myProcessor-out

This will result in the following message flow:

myPublisher → myProcessor → mySubscriber

A supplier is triggered by Spring Cloud Stream by default every second, so we could expect output like the following if we start a Spring Boot application including the functions and configuration described previously:

ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:30 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:31 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:32 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:33 CET 2021

In cases where the supplier should be triggered by an external event instead of using a timer, the StreamBridge helper class can be used. For example, if a message should be published to the processor when a REST API, sampleCreateAPI, is called, the code could look like the following:

@Autowired
private StreamBridge streamBridge;
@PostMapping
void sampleCreateAPI(@RequestBody String body) {
  streamBridge.send("myProcessor-in-0", body);
}

Now that we understand the various Spring APIs, let’s learn a bit about Docker and containers in the next section.

Docker

I assume that Docker and the concept of containers need no in-depth introduction. Docker made the concept of containers as a lightweight alternative to virtual machines very popular in 2013. A container is actually a process in a Linux host that uses Linux namespaces to provide isolation between different containers, in terms of their use of global system resources such as users, processes, filesystems, and networking. Linux control groups (also known as cgroups) are used to limit the amount of CPU and memory that a container is allowed to consume.

Compared to a virtual machine that uses a hypervisor to run a complete copy of an operating system in each virtual machine, the overhead in a container is a fraction of the overhead in a traditional virtual machine.

This leads to much faster startup times and significantly lower overhead in terms of CPU and memory usage.

The isolation that’s provided for a container is, however, not considered to be as secure as the isolation that’s provided for a virtual machine. With the release of Windows Server 2016, Microsoft supports the use of Docker in Windows servers.

During the last few years, a lightweight form of virtual machines has evolved. It mixes the best of traditional virtual machines and containers, providing virtual machines with a footprint and startup time similar to containers and with the same level of secure isolation provided by traditional virtual machines. Some examples are Amazon Firecracker and Microsoft Windows Subsystem for Linux v2 (WSL2). For more information, see https://firecracker-microvm.github.io and https://docs.microsoft.com/en-us/windows/wsl/.

Containers are very useful during both development and testing. Being able to start up a complete system landscape of cooperating microservices and resource managers (for example, database servers, messaging brokers, and so on) with a single command for testing is simply amazing.

For example, we can write scripts in order to automate end-to-end tests of our microservice landscape. A test script can start up the microservice landscape, run tests using the exposed APIs, and tear down the landscape. This type of automated test script is very useful, both for running locally on a developer PC before pushing code to a source code repository, and to be executed as a step in a delivery pipeline. A build server can run these types of tests in its continuous integration and deployment process whenever a developer pushes code to the source repository.

For production usage, we need a container orchestrator such as Kubernetes. We will come back to container orchestrators and Kubernetes later in this book.

For most of the microservices we will look at in this book, a Dockerfile such as the following is all that is required to run the microservice as a Docker container:

FROM openjdk:17
MAINTAINER Magnus Larsson <magnus.larsson.ml@gmail.com>
EXPOSE 8080
ADD ./build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

If we want to start and stop many containers with one command, Docker Compose is the perfect tool. Docker Compose uses a YAML file to describe the containers to be managed.

For our microservices, it might look something like the following:

product:
 build: microservices/product-service
recommendation:
 build: microservices/recommendation-service
review:
  build: microservices/review-service
composite:
  build: microservices/product-composite-service
  ports:
    - "8080:8080"

Let me explain the preceding source code a little:

  • The build directive is used to specify which Dockerfile to use for each microservice. Docker Compose will use it to build a Docker image and then launch a Docker container based on that Docker image.
  • The ports directive for the composite service is used to expose port 8080 on the server where Docker runs. On a developer’s machine, this means that the port of the composite service can be reached simply by using localhost:8080!

All the containers in the YAML files can be managed with simple commands such as the following:

  • docker-compose up -d: Starts all containers. -d means that the containers run in the background, not locking the terminal from where the command was executed.
  • docker-compose down: Stops and removes all containers.
  • docker-compose logs -f --tail=0: Prints out log messages from all containers. -f means that the command will not complete, and instead waits for new log messages. --tail=0 means that we don’t want to see any previous log messages, only new ones.

For a full list of Docker Compose commands, see https://docs.docker.com/compose/reference/.

This was a brief introduction to Docker. We will go into more detail about Docker starting with Chapter 4, Deploying Our Microservices Using Docker.

Summary

In this chapter, we have been introduced to Spring Boot and complementary open source tools that can be used to build cooperating microservices.

Spring Boot is used to simplify the development of Spring-based, production-ready applications, such as microservices. It is strongly opinionated in terms of how to set up both core modules from the Spring Framework and third-party tools. Using Spring WebFlux, we can develop microservices that expose reactive, that is, non-blocking, REST services. To document these REST services, we can use springdoc-openapi to create OpenAPI-based documentation for the APIs. If we need to persist data used by the microservices, we can use Spring Data, which provides an elegant abstraction for accessing and manipulating persistent data using entities and repositories. Spring Data’s programming model is similar, but not fully portable between different types of databases, for example, relational, document, key-value, and graph databases.

If we prefer sending messages asynchronously between our microservices, we can use Spring Cloud Stream, which provides a streaming abstraction over messaging. Spring Cloud Stream comes with out-of-the-box support for Apache Kafka and RabbitMQ but can be extended to support other messaging brokers using custom binders. Finally, Docker makes the concept of containers as a lightweight alternative to virtual machines easy to use. Based on Linux namespaces and control groups, containers provide isolation similar to what traditional virtual machines provide, but with a significantly lower overhead in terms of CPU and memory usage.

In the next chapter, we will take our first small steps, creating microservices with minimalistic functionality using Spring Boot and Spring WebFlux.

Questions

  1. What is the purpose of the @SpringBootApplication annotation?
  2. What are the main differences between the older Spring component for developing REST services, Spring Web MVC, and the new Spring WebFlux?
  3. How does springdoc-openapi help a developer document REST APIs?
  4. What is the function of a repository in Spring Data and what is the simplest possible implementation of a repository?
  5. What is the purpose of a binder in Spring Cloud Stream?
  6. What is the purpose of Docker Compose?
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Build cloud-native production-ready microservices and stay ahead of the curve
  • Understand the challenges of building large-scale microservice architectures
  • Learn how to get the best out of the latest updates, including Spring Boot 3, Spring Cloud, Kubernetes, and Istio

Description

Looking to build and deploy microservices but not sure where to start? Check out Microservices with Spring Boot 3 and Spring Cloud, Third Edition. With a practical approach, you'll begin with simple microservices and progress to complex distributed applications. Learn essential functionality and deploy microservices using Kubernetes and Istio. This book covers Java 17, Spring Boot 3, and Spring Cloud 2022. Java EE packages are replaced with the latest Jakarta EE packages. Code examples are updated and deprecated APIs have been replaced, providing the most up to date information. Gain knowledge of Spring's AOT module, observability, distributed tracing, and Helm 3 for Kubernetes packaging. Start with Docker Compose to run microservices with databases and messaging services. Progress to deploying microservices on Kubernetes with Istio. Explore persistence, resilience, reactive microservices, and API documentation with OpenAPI. Learn service discovery with Netflix Eureka, edge servers with Spring Cloud Gateway, and monitoring with Prometheus, Grafana, and the EFK stack. By the end, you'll build scalable microservices using Spring Boot and Spring Cloud.

What you will learn

Build reactive microservices using Spring Boot Develop resilient and scalable microservices using Spring Cloud Use OAuth 2.1/OIDC and Spring Security to protect public APIs Implement Docker to bridge the gap between development, testing, and production Deploy and manage microservices with Kubernetes Apply Istio for improved security, observability, and traffic management Write and run automated microservice tests with JUnit, test containers, Gradle, and bash Use Spring AOT and GraalVM to native compile the microservices Use Micrometer Tracing for distributed tracing

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Aug 31, 2023
Length 706 pages
Edition : 3rd Edition
Language : English
ISBN-13 : 9781805128694
Category :
Concepts :

Table of Contents

26 Chapters
Preface Chevron down icon Chevron up icon
Introduction to Microservices Chevron down icon Chevron up icon
Introduction to Spring Boot Chevron down icon Chevron up icon
Creating a Set of Cooperating Microservices Chevron down icon Chevron up icon
Deploying Our Microservices Using Docker Chevron down icon Chevron up icon
Adding an API Description Using OpenAPI Chevron down icon Chevron up icon
Adding Persistence Chevron down icon Chevron up icon
Developing Reactive Microservices Chevron down icon Chevron up icon
Introduction to Spring Cloud Chevron down icon Chevron up icon
Adding Service Discovery Using Netflix Eureka Chevron down icon Chevron up icon
Using Spring Cloud Gateway to Hide Microservices behind an Edge Server Chevron down icon Chevron up icon
Securing Access to APIs Chevron down icon Chevron up icon
Centralized Configuration Chevron down icon Chevron up icon
Improving Resilience Using Resilience4j Chevron down icon Chevron up icon
Understanding Distributed Tracing Chevron down icon Chevron up icon
Introduction to Kubernetes Chevron down icon Chevron up icon
Deploying Our Microservices to Kubernetes Chevron down icon Chevron up icon
Implementing Kubernetes Features to Simplify the System Landscape Chevron down icon Chevron up icon
Using a Service Mesh to Improve Observability and Management Chevron down icon Chevron up icon
Centralized Logging with the EFK Stack Chevron down icon Chevron up icon
Monitoring Microservices Chevron down icon Chevron up icon
Installation Instructions for macOS Chevron down icon Chevron up icon
Installation Instructions for Microsoft Windows with WSL 2 and Ubuntu Chevron down icon Chevron up icon
Native-Complied Java Microservices Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Full star icon 5
(1 Ratings)
5 star 100%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


taeksoo shin Feb 6, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Feefo Verified review Feefo image
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.