A Beginner's Guide to Microservice Architecture with Example

A Beginner's Guide to Microservice Architecture with Example

A Beginner's Guide to Microservice Architecture - Learn Microservice Architecture in simple and easy steps starting from basic to advanced concepts with examples

Learn Microservice Architecture in simple and easy steps starting from basic to advanced concepts with examples

What Is Microservices – Introduction To Microservice Architecture

Have you ever wondered, What is Microservices and how the scaling industries integrate with them while building applications to keep up with their client expectations?

To get an idea of What is Microservices, you have to understand how a monolithic application is decomposed into small tiny micro applications which are packaged and deployed independently. This blog will clear your understanding of how developers use microservices to scale their applications according to their need.

In this article, you will learn about the following:
Why Microservices?What Is Microservices?Features Of Microservice ArchitectureAdvantages Of Microservice ArchitectureBest Practices To Design MicroservicesCompanies Using Microservices##

Why Microservices?

Now, before I tell you about microservices, let’s see the architecture that prevailed before microservices i.e. the Monolithic Architecture.

In layman terms, you can say that its similar to a big container wherein all the software components of an application are assembled together and tightly packaged.

Listed down are the challenges of Monolithic Architecture:

Figure 1: What Is Microservices – Challenges of Monolithic Architecture
Inflexible – Monolithic applications cannot be built using different technologies Unreliable – Even if one feature of the system does not work, then the entire system does not workUnscalable – Applications cannot be scaled easily since each time the application needs to be updated, the complete system has to be rebuiltBlocks Continous Development – Many features of the applications cannot be built and deployed at the same timeSlow Development – Development in monolithic applications take lot of time to be built since each and every feature has to be built one after the other**Not Fit For Complex Applications – **Features of complex applications have tightly coupled dependencies
The above challenges were the main reasons that led to the evolution of microservices.

What Is Microservices?

Microservices, aka Microservice Architecture, is an architectural style that structures an application as a collection of small autonomous services, modeled around a business domain.

** Figure 2**: What Is Microservices – Microservices Representation

In Microservice Architecture, each service is self-contained and implements a single business capability.

Differences Between Traditional Architecture and Microservices

Consider an E-commerce application as a use-case to understand the difference between both of them.

Figure 3: What Is Microservices – Differences Between Monolithic Architecture and Microservices

The main difference we observe in the above diagram is that all the features initially were under a single instance sharing a single database. But then, with microservices, each feature was allotted a different microservice, handling their own data, and performing different functionalities.

Now, let us understand more about microservices by looking at its architecture. Refer the diagram below:

Microservice Architecture

Figure 4: What Is Microservices – Microservice Architecture
Different clients from different devices try to use different services like search, build, configure and other management capabilitiesAll the services are separated based on their domains and functionalities and are further allotted to individual microservicesThese microservices have their own load balancer and execution environment to execute their functionalities & at the same time captures data in their own databasesAll the microservices communicate with each other through a stateless server which is either REST or Message BusMicroservices know their path of communication with the help of **Service Discovery **and perform operational capabilities such as automation, monitoringThen all the functionalities performed by microservices are communicated to clients via API GatewayAll the internal points are connected from the API Gateway. So, anybody who connects to the API Gateway automatically gets connected to the complete system
Now, let us learn more about microservices by looking at its features.

Microservices Features

Figure 5: What Is Microservices – Features Of Microservices
Decoupling – Services within a system are largely decoupled. So the application as a whole can be easily built, altered, and scaledComponentization – Microservices are treated as independent components that can be easily replaced and upgradedBusiness Capabilities – Microservices are very simple and focus on a single capability Autonomy – Developers and teams can work independently of each other, thus increasing speedContinous Delivery – Allows frequent releases of software, through systematic automation of software creation, testing, and approval Responsibility – Microservices do not focus on applications as projects. Instead, they treat applications as products for which they are responsible Decentralized Governance – The focus is on using the right tool for the right job. That means there is no standardized pattern or any technology pattern. Developers have the freedom to choose the best useful tools to solve their problems Agility – Microservices support agile development. Any new feature can be quickly developed and discarded again##

Advantages Of Microservices

Figure 6: What Is Microservices – Advantages Of Microservices
Independent Development – All microservices can be easily developed based on their individual functionalityIndependent Deployment – Based on their services, they can be individually deployed in any application Fault Isolation – Even if one service of the application does not work, the system still continues to functionMixed Technology Stack – Different languages and technologies can be used to build different services of the same applicationGranular Scaling – Individual components can scale as per need, there is no need to scale all components together##

Best Practices To Design Microservices

In today’s world, complexity has managed to creep into products. Microservice architecture promises to keep teams scaling and function better.

The following are the best practices to design microservices:

Figure 7: What Is Microservices – Best Practices To Design Microservices

Now, let us look at a use-case to get a better understanding of microservices.

Use-Case: Shopping Cart Application

Let’s take a classic use case of a shopping cart application.

When you open a shopping cart application, all you see is just a website. But, behind the scenes, the shopping cart application has a service for accepting payments, a service for customer services and so on.

Assume that developers of this application have created it in a monolithic framework.Refer to the diagram below:

Figure 8: What Is Microservices – Monolithic Framework Of Shopping Cart Application

So, all the features are put together in a single code base and are under a single underlying database.

Now, let’s suppose that there is a new brand coming up in the market and developers want to put all the details of the upcoming brand in this application.

Then, they not only have to rework on the service for new labels, but they also have to reframe the complete system and deploy it accordingly.

To avoid such challenges developers of this application decided to shift their application from a monolithic architecture to microservices.Refer to the diagram below to understand the microservices architecture of shopping cart application

Figure 9: What Is Microservices – Microservice Architecture Of Shopping Cart Application

This means that developers don’t create a web microservice, a logic microservice, or a database microservice. Instead, they create separate microservices for search, recommendations, customer services and so on.

This type of architecture for the application not only helps the developers to overcome all the challenges faced with the previous architecture but also helps the shopping cart application to be built, deployed, and scale up easily.

Companies using Microservices

There is a long list of companies using Microservices to build applications, these are just to name a few:

Figure 10: What Is Microservices – Companies Using Microservices

Microservices Tutorial – Learn all about Microservices with Example Microservices Tutorial

Organizations are quickly moving towards Microservices architecture & hunting for professionals with Microservices Certification. I hope that you have read my previous blog on ***What is Microservices ***that explains the architecture, compares microservices with monolithic and SOA, and also explores when to use microservices with the help of use-cases.

In this Microservices tutorial, the following topics will be covered:

  • Monolithic Architecture
  • Challenges of Monolithic Architecture
  • What is Microservices
  • Microservice Architecture
  • Microservices Example – Demo

Let us explore the concepts of microservices through a use-case on Mediamore.com.

Mediamore is an entertainment company which provides streaming media and videos online. It consists of various genres of TV Shows in different languages. Like many other companies mediamore started its journey with a monolithic architecture.

Let us now explore the monolithic framework of mediamore.

Monolithic Architecture

Figure 11: Monolithic Architecture of Mediamore – Microservices Tutorial

Refer to the above diagram. We can infer that all the features such as the search, user-info, recommendations, video playlist and others are put on a single database using single code.

Now, let me tell you the challenges faced by the developers while using a monolithic framework by using some scenarios.

Challenges of Monolithic Architecture

Scenario 1: Scalability:* Let’s assume that the developers want to update the playlist according to most popular tv shows and also simultaneously want to update all videos to HD quality.*

The developers cannot scale the application simultaneously. New instances of the same application have to be created every time a new feature has to be developed or deployed.

Scenario 2: Agility:* Assume that developers want to make immediate changes in the application.*

The monolithic application can definitely accommodate these changes. But, the problem here is that the developers have to rebuild the code for every small change.

Scenario 3: Hybrid Technologies: Suppose developers of this application are comfortable with various technologies like JAVA, C++,.NET, C#.

Even though they are comfortable with various technologies, they still have to build large and complex applications on a single technology.

Scenario 4: Fault Tolerance:* Let’s suppose that a specific feature is not working in the application.*

The complete system goes down because of this problem. In order to tackle this problem, the application has to be re-built, re-tested and also re-deployed.

So, how did the developers of mediamore overcome these complexities?

Developers thus decided to re-architect their monolithic application into multiple individual deployable components, called as microservices.

Here lies the million dollar question!

What is Microservices?

Microservices is an architecture wherein all the components of the system are put into individual components, which can be built, deployed, and scaled individually.

Let me explain you with a simple analogy.

You must have seen how bees build their honeycomb by aligning hexagonal wax cells. They initially start with a small section using various materials and continue to build a large beehive out of it. These cells form a pattern resulting in a strong structure which holds together a particular section of the beehive. Here, each cell is independent of the other but it is also correlated with the other cells. This means that damage to one cell does not damage the other cells, so, bees can reconstruct these cells without impacting the complete beehive.

Figure 12: Beehive Representation of Microservices – Microservices Tutorial

Refer to the above diagram. Here, each hexagonal shape represents an individual service component. Similar to the working of bees, each agile team builds an individual service component with the available frameworks and the chosen technology stack. Just as in a beehive, each service component forms a strong microservice architecture to provide better scalability. Also, issues with each service component can be handled individually by the agile team with no or minimal impact on the entire application.

The next question that may come to your mind is how do the different components of microservice architecture work together.

But, before that, let me list down the components of the microservice architecture.

Refer to the below diagram.

Figure 13: Microservices Architecture – Microservices Tutorial
Clients – Different users from various devices send requests. Identity Providers – Authenticates user or clients identities and issues security tokens.API Gateway – Handles client requests.Static Content – Houses all the content of the system.Management – Balances services on nodes and identifies failures.Service Discovery – A guide to find the route of communication between microservices.* Content Delivery Networks – Distributed network of proxy servers and their data centers.

  • Remote Service – Enables the remote access information that resides on a network of IT devices.

Let me now brief you on how these components work together on mediamore by considering a scenario.

Microservice Architecture

Scenario:

Alice is an avid user of mediamore. She uses mediamore regularly to watch her favorite series online. She recently missed watching an episode of her favorite TV show.

When Alice logs in to the application, she sees the most recommended content on her home page. After some searching, she finally finds her TV Show.

But, what if Alice wants to get her TV Show with a single click.

How will the developers work together to fulfill Alice’s request?

Alice’s request is passed on to the Identity Provider. Identity provider thus authenticates Alice’s request by identifying her as a regular user on mediamore.

These requests are passed to the API Gateway which acts as an entry point for Alice to forward her requests to the appropriate microservices.

Each feature has its own working microservice, handling their own data. These microservices also have their own** load balancers** and **execution environments **to function properly.

Figure 14: Microservices Architecture of Mediamore – Microservices Tutorial

Refer to the diagram below. Each microservice is handled by a small agile team such as content team, video uploading team, most trending team, search team etc.

Figure 15: Division of Teams of Mediamore – Microservices Tutorial

  • The content team consists of millions of TV Shows that the application provides.
  • The video uploading team have the responsibility to upload all the content into the application
  • The most trending team houses the most trending shows according to the geographical location of users and so on.

These small teams of developers relate each and every piece of content with the metadata that describes the searched content. Then, metadata is fed into another microservice i.e. the search function which ensures Alice’s search results are captures into the content catalog.

Then, the third microservice i.e. most trending microservice captures the trending content among all the mediamore users according to their geographical locations.

The content from this microservice is what Alice sees when she first logs into mediamore.

These individually deployable microservices are put in specific containers to join the application. Containers are used to deliver the code to the sector where deployment is required.

But before they join the application to work together, they have to find each other to fulfil Alice’s request.

How do these microservices find each other?

Microservices use service discovery which acts as a guide to find the route of communication between each of them. Microservices then communicate with each other via a stateless server i.e. either by HTTP Request/Message Bus.

Figure 16: Communication Between Microservices – Microservices Tutorial

These microservices communicate with each other using an Application Program Interface(API). After the Microservices communicate within themselves, they deploy the static content to a cloud-based storage service that can deliver them directly to the clients via Content Delivery Networks (CDNs).

So, when Alice searches for her TV Show, the search microservice communicates with the content catalog service in API about what is Alice searching for and then these microservices compare the typed words with the metadata they already have.

Figure 17: Representation of how to search operation is performed with the help of API – Microservices Tutorial

Once the teams of developers capture the most typed words by Alice, the analytics team update the code in recommendations microservice and compare Alice’s most viewed content and preferences to popular content among other users in the same geographical region.

This means that the next time Alice logs on to the application, she not only sees the most popular content but also finds a personalized playlist which contains the shows she has previously viewed.

In this way, Alice’s request is fulfilled by the development team in a quick manner as they did not have to build the complete application again and just had to update the code to deploy this new functionality.

So this way microservices invoke parallel environments to satisfy millions of customers with varying interests.

Microservices Architecture Example – Demo

To demonstrate the concepts of microservices, I have created 3 Maven Projects called as Doctor_Microservice_Edureka, Diagnosis_Microservice_Edureka, and Patient_Microservice_Edureka using Spring Boot.

Refer to the snapshot below.

Before you understand how these 3 projects interact with each other. Let me brief you on the files of these projects.

To explain this I will consider the project Patient_Microservice_Edureka and list down its basic files.

Refer to the snapshot below.

Pom.xml – Dependencies are added for the creation of REST services.

Application.java – Identical class in all three projects. Acts as an initiator to Spring Boot.

ApplicationConfiguration.java – Research configuration class responsible for exposing REST services for application users.

Patient.java – A simple class consisting of input such as the patient’s name, id, email.

PatientRest.java – Starts the implementation of the REST services in the project.

In this way, similar files are created for the other 2 projects with some additional files in Doctor_Microservice.

REST services are thus created to search patients and the diagnosis. Keys of patient and diagnosis are passed as a parameter to a method(PatientDetails) in Doctor_Microservice.This method gets the data of the patients and diseases.

Refer to the snapshot above. Here, we observe that Patient and Diagnosis classes are included in Doctor_Microservice_Edureka. These classes are cloned from their original projects.

Then to start the REST services, I have initialized **Patient_Microservice_Edureka **on port 8081, Diagnosis_Microservice_Edureka on 8082 and the Doctor_Microservice_Edureka on port 8083.

With the above configurations, when I run each service simultaneously, 3 different console windows run in the Eclipse console window.

As you can see below that after we run the projects, Spring Boot generates a boot log. This log consists information on initializing tomcat and its associated resources. The last line of the log indicates us whether our application has started or not.

To test if the patient and diagnosis services are functioning properly, one a browser (Mozilla Firefox) and go to http://localhost:8081/ and** **http://localhost:8082/ URLs. You should get the outputs as shown below:

Finally, to test the functionality of the Doctor_Microservice_Edureka, I simulated the information of the patient with id 2, suffering from a disease of id 3 and consulting a doctor with consultation 4.

The URL used is as follows: http://localhost:8083/doctor?idPatient=2&idDiagnosis=3&consultation=4

Refer to the snapshot below for the output.

In this way, 3 Microservices interact with each other to produce the desired results.


I hope you have enjoyed reading this Microservices Tutorial. We have seen a simple example to understand how different microservices communicate with each other.

Build a reactive Microservices Architecture using Spring Cloud Gateway, Spring Boot and Spring WebFlux

Build a reactive Microservices Architecture using Spring Cloud Gateway, Spring Boot and Spring WebFlux

In this article, you'll learn how you can build a reactive microservices architecture using Spring Cloud Gateway, Spring Boot, and Spring WebFlux.

Originally published by Matt Raible at https://developer.okta.com

So you wanna go full reactive, eh? Great! Reactive programming is an increasingly popular way to make your applications more efficient. Instead of making a call to a resource and waiting on a response, reactive applications asynchronously receive a response. This allows them to free up processing power, only perform processing when necessary, and scale more effectively than other systems.

The Java ecosystem has its fair share of reactive frameworks, including Play Framework, Ratpack, Vert.x, and Spring WebFlux. Like Reactive programming, a microservices architecture can help large teams scale quickly and is possible to build using any of the awesome aforementioned frameworks.

Today I’d like to show you how you can build a reactive microservices architecture using Spring Cloud Gateway, Spring Boot, and Spring WebFlux. We’ll leverage Spring Cloud Gateway as API gateways are often important components in a cloud-native microservices architecture, providing the aggregation layer for all your backend microservices.

This tutorial shows you how to build a microservice with a REST API that returns a list of new cars. You’ll use Eureka for service discovery and Spring Cloud Gateway to route requests to the microservice. Then you’ll integrate Spring Security so only authenticated users can access your API gateway and microservice.

Prerequisites: HTTPie (or cURL), Java 11+, and an internet connection.

Spring Cloud Gateway vs. Zuul

Zuul is Netflix’s API gateway. First released in 2013, Zuul was not originally reactive, but Zuul 2 is a ground-up rewrite to make it reactive. Unfortunately, Spring Cloud does not support Zuul 2 and it likely never will.

Spring Cloud Gateway is now the preferred API gateway implementation from the Spring Cloud Team. It’s built on Spring 5, Reactor, and Spring WebFlux. Not only that, it also includes circuit breaker integration, service discovery with Eureka, and is much easier to integrate with OAuth 2.0!

Let’s dig in.

Create a Spring Cloud Eureka Server Project

Start by creating a directory to hold all your projects, for example, spring-cloud-gateway. Navigate to it in a terminal window and create a discovery-service project that includes Spring Cloud Eureka Server as a dependency.

http https://start.spring.io/starter.zip javaVersion==11 artifactId==discovery-service \  name==eureka-service baseDir==discovery-service \  dependencies==cloud-eureka-server | tar -xzvf -
The command above uses HTTPie. I highly recommend installing it. You can also use curl. Run curl https://start.spring.io to see the syntax.

Add @EnableEurekaServer on its main class to enable it as a Eureka server.

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer

@SpringBootApplication

public class EurekaServiceApplication {...}

Add the following properties to the project’s src/main/resources/application.properties file to configure its port and turn off Eureka registration.

server.port=8761
eureka.client.register-with-eureka=false

To make the discovery-service run on Java 11+, add a dependency on JAXB.

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
</dependency>

Start the project using ./mvnw spring-boot:run or by running it in your IDE.

Create a Spring Cloud Gateway Project

Next, create an api-gateway project that includes a handful of Spring Cloud dependencies.

http https://start.spring.io/starter.zip javaVersion==11 artifactId==api-gateway 
 name==api-gateway baseDir==api-gateway
 dependencies==actuator,cloud-eureka,cloud-feign,cloud-gateway,cloud-hystrix,webflux,lombok | tar -xzvf -

We’ll come back to configuring this project in a minute.

Create a Reactive Microservice with Spring WebFlux

The car microservice will contain a significant portion of this example’s code because it contains a fully-functional REST API that supports CRUD (Create, Read, Update, and Delete).

Create the car-service project using start.spring.io:

http https://start.spring.io/starter.zip javaVersion==11 artifactId==car-service 
 name==car-service baseDir==car-service
 dependencies==actuator,cloud-eureka,webflux,data-mongodb-reactive,flapdoodle-mongo,lombok | tar -xzvf -

The dependencies argument is interesting in this command. You can see that Spring WebFlux is included, as is MongoDB. Spring Data provides reactive drivers for Redis and Cassandra as well.

You may also be interested in R2DBC (Reactive Relational Database Connectivity) - an endeavor to bring a reactive programming API to SQL databases. I did not use it in this example because it’s not yet available on start.spring.io.

Build a REST API with Spring WebFlux

I’m a big fan of VWs, especially classic ones like the bus and the bug. Did you know that VW has a bunch of electric vehicles coming out in the next few years? I’m really excited by the ID Buzz! It has classic curves and is all-electric. It even has 350+ horsepower!

In case you’re not familiar with the ID Buzz, here’s a photo from Volkswagen.

Let’s have some fun with this API example and use the electric VWs for our data set. This API will track the various car names and release dates.

Add Eureka registration, sample data initialization, and a reactive REST API to src/main/java/…​/CarServiceApplication.java:

package com.example.carservice;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Set;
import java.util.UUID;

@EnableEurekaClient
@SpringBootApplication
@Slf4j
public class CarServiceApplication {

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

   @Bean
   ApplicationRunner init(CarRepository repository) {
       // Electric VWs from
https://www.vw.com/electric-concepts/
       // Release dates from
https://www.motor1.com/features/346407/volkswagen-id-price-on-sale/
       Car ID = new Car(UUID.randomUUID(), "ID.", LocalDate.of(2019, Month.DECEMBER, 1));
       Car ID_CROZZ = new Car(UUID.randomUUID(), "ID. CROZZ", LocalDate.of(2021, Month.MAY, 1));
       Car ID_VIZZION = new Car(UUID.randomUUID(), "ID. VIZZION", LocalDate.of(2021, Month.DECEMBER, 1));
       Car ID_BUZZ = new Car(UUID.randomUUID(), "ID. BUZZ", LocalDate.of(2021, Month.DECEMBER, 1));
       Set<Car> vwConcepts = Set.of(ID, ID_BUZZ, ID_CROZZ, ID_VIZZION);

       return args -> {
           repository
                   .deleteAll()
                   .thenMany(
                           Flux
                                   .just(vwConcepts)
                                   .flatMap(repository::saveAll)
                   )
                   .thenMany(repository.findAll())
                   .subscribe(car -> log.info("saving " + car.toString()));
       };
   }
}

@Document
@Data
@NoArgsConstructor
@AllArgsConstructor
class Car {
   @Id
   private UUID id;
   private String name;
   private LocalDate releaseDate;
}

interface CarRepository extends ReactiveMongoRepository<Car, UUID> {
}

@RestController
class CarController {

   private CarRepository carRepository;

   public CarController(CarRepository carRepository) {
       this.carRepository = carRepository;
   }

   @PostMapping("/cars")
   @ResponseStatus(HttpStatus.CREATED)
   public Mono<Car> addCar(@RequestBody Car car) {
       return carRepository.save(car);
   }

   @GetMapping("/cars")
   public Flux<Car> getCars() {
       return carRepository.findAll();
   }

   @DeleteMapping("/cars/{id}")
   public Mono<ResponseEntity<Void>> deleteCar(@PathVariable("id") UUID id) {
       return carRepository.findById(id)
               .flatMap(car -> carRepository.delete(car)
                       .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
               )
               .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
   }
}

  1. Add the @EnableEurekaClient annotation for service discovery
  2. @Slf4j is a handy annotation from Lombok to enable logging in a class
  3. ApplicationRunner bean to populate MongoDB with default data
  4. Delete all existing data in MongoDB so new data is not additive
  5. Subscribe to results so both deleteAll() and saveAll() are invoked
  6. Car class with Spring Data NoSQL and Lombok annotations to reduce boilerplate
  7. CarRepository interface that extends ReactiveMongoRepository, giving you CRUDability with hardly any code!
  8. CarController class that uses CarRepository to perform CRUD actions
  9. Spring WebFlux returns a Mono publisher for single objects
  10. Return a Flex publisher for multiple objects

You’ll also need to modify the car-service project’s application.properties to set its name and port.

spring.application.name=car-service
server.port=8081

Run MongoDB

The easiest way to run MongoDB is to remove the test scope from the flapdoodle dependency in car-service/pom.xml. This will cause your app to start an embedded MongoDB dependency.

<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <!--<scope>test</scope>-->
</dependency>

You can also install and run MongoDB using Homebrew.

brew tap mongodb/brew
brew install [email protected]
mongod

Or, use Docker:

docker run -d -it -p 27017:27017 mongo

Stream Data with WebFlux

This completes everything you need to do to build a REST API with Spring WebFlux.

"But wait!" you might say. "I thought WebFlux was all about streaming data?"

In this particular example, you can still stream data from the /cars endpoint, but not in a browser.

A browser has no way to consume a stream other than using Server-Sent Events or WebSockets. Non-browser clients however can get a JSON stream by sending an Accept header with a value of application/stream+json .

You could test everything works at this point by firing up your browser and using HTTPie to make requests. However, it’s much better to write automated tests!

Test Your WebFlux API with WebTestClient

WebClient ships as part of Spring WebFlux and can be useful for making reactive requests, receiving responses, and populating objects with the payload. A companion class, WebTestClient, can be used to test your WebFlux API. It contains request methods that are similar to WebClient, as well as methods to check the response body, status, and headers.

Modify the src/test/java/…​/CarServiceApplicationTests.java class in the car-service project to contain the code below.

package com.example.carservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Collections;
import java.util.UUID;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class CarServiceApplicationTests {

    @Autowired
    CarRepository carRepository;

    @Autowired
    WebTestClient webTestClient;

    @Test
    public void testAddCar() {
        Car buggy = new Car(UUID.randomUUID(), "ID. BUGGY", LocalDate.of(2022, Month.DECEMBER, 1));

        webTestClient.post().uri("/cars")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .body(Mono.just(buggy), Car.class)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBody()
                .jsonPath("$.id").isNotEmpty()
                .jsonPath("$.name").isEqualTo("ID. BUGGY");
    }

    @Test
    public void testGetAllCars() {
        webTestClient.get().uri("/cars")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBodyList(Car.class);
    }

    @Test
    public void testDeleteCar() {
        Car buzzCargo = carRepository.save(new Car(UUID.randomUUID(), "ID. BUZZ CARGO",
                LocalDate.of(2022, Month.DECEMBER, 2))).block();

        webTestClient.delete()
                .uri("/cars/{id}", Collections.singletonMap("id", buzzCargo.getId()))
                .exchange()
                .expectStatus().isOk();
    }
}

To prove it works, run ./mvnw test. Give yourself a pat on the back when your tests pass!

If you’re on Windows, use mvnw test.

Use Spring Cloud Gateway with Reactive Microservices

To edit all three projects in the same IDE window, I find it useful to create an aggregator pom.xml. Create a pom.xml file in the parent directory of your projects and copy the XML below into it.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.okta.developer</groupId>
    <artifactId>reactive-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>reactive-parent</name>
    <modules>
        <module>discovery-service</module>
        <module>car-service</module>
        <module>api-gateway</module>
    </modules>
</project>

After creating this file, you should be able to open it in your IDE as a project and navigate between projects easily.

In the api-gateway project, add @EnableEurekaClient to the main class to make it Eureka-aware.

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class ApiGatewayApplication {...}

Then, modify the src/main/resources/application.properties file to configure the application name.

spring.application.name=gateway

Create a RouteLocator bean in ApiGatewayApplication to configure routes. You can configure Spring Cloud Gateway with YAML, but I prefer Java.

package com.example.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

@EnableEurekaClient
@SpringBootApplication
public class ApiGatewayApplication {

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

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("car-service", r -> r.path("/cars")
                        .uri("lb://car-service"))
                .build();
    }
}

After making these code changes, you should be able to start all three Spring Boot apps and hit http://localhost:8080/cars.

$ http :8080/cars
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
transfer-encoding: chunked

[
    {
        "id": "ff48f617-6cba-477c-8e8f-2fc95be96416",
        "name": "ID. CROZZ",
        "releaseDate": "2021-05-01"
    },
    {
        "id": "dd6c3c32-724c-4511-a02c-3348b226160a",
        "name": "ID. BUZZ",
        "releaseDate": "2021-12-01"
    },
    {
        "id": "97cfc577-d66e-4a3c-bc40-e78c3aab7261",
        "name": "ID.",
        "releaseDate": "2019-12-01"
    },
    {
        "id": "477632c8-2206-4f72-b1a8-e982e6128ab4",
        "name": "ID. VIZZION",
        "releaseDate": "2021-12-01"
    }
]

Add a REST API to Retrieve Your Favorite Cars

Create a /fave-cars endpoint that strips out cars that aren’t your favorite.

First, add a load-balanced WebClient.Builder bean.

@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder();
}

Then add a Car POJO and a FaveCarsController below the ApiGatewayApplication class in the same file.

public class ApiGatewayApplication {...}
class Car {...}
class FaveCarsController {...}

Use WebClient to retrieve the cars and filter out the ones you don’t love.

@Data
class Car {
    private String name;
    private LocalDate releaseDate;
}

@RestController
class FaveCarsController {

    private final WebClient.Builder carClient;

    public FaveCarsController(WebClient.Builder carClient) {
        this.carClient = carClient;
    }

    @GetMapping("/fave-cars")
    public Flux<Car> faveCars() {
        return carClient.build().get().uri("lb://car-service/cars")
                .retrieve().bodyToFlux(Car.class)
                .filter(this::isFavorite);
    }

    private boolean isFavorite(Car car) {
        return car.getName().equals("ID. BUZZ");
    }
}

If you’re not using an IDE that auto-imports for you, you’ll want to copy/paste the following into the top of ApiGatewayApplication.java:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

Restart your gateway app to see the http://localhost:8080/fave-cars endpoint only returns the ID Buzz.

What about Failover with Hystrix?

Spring Cloud Gateway only supports Hystrix at the time of this writing. Spring Cloud deprecated direct support for Hystrix in favor of Spring Cloud Circuit Breaker. Unfortunately, this library hasn’t had a GA release yet, so I decided not to use it.

To use Hystrix with Spring Cloud Gateway, you can add a filter to your car-service route, like so:

.route("car-service", r -> r.path("/cars")
        .filters(f -> f.hystrix(c -> c.setName("carsFallback")
                .setFallbackUri("forward:/cars-fallback")))
        .uri("lb://car-service/cars"))
.build();

Then create a CarsFallback controller to handle the /cars-fallback route.

@RestController
class CarsFallback {

    @GetMapping("/cars-fallback")
    public Flux<Car> noCars() {
        return Flux.empty();
    }
}

First, restart your gateway and confirm http://localhost:8080/cars works. Then shut down the car service, try again, and you’ll see it now returns an empty array. Restart the car service and you’ll see the list populated again.

You’ve built a resilient and reactive microservices architecture with Spring Cloud Gateway and Spring WebFlux. Now let’s see how to secure it!

What about Feign with Spring Cloud Gateway?

If you’d like to use Feign in a WebFlux app, see the feign-reactive project. I did not have a need for Feign in this particular example.

Secure Spring Cloud Gateway with OAuth 2.0

OAuth 2.0 is an authorization framework for delegated access to APIs. OIDC (or OpenID Connect) is a thin layer on top of OAuth 2.0 that provides authentication. Spring Security has excellent support for both frameworks and so does Okta!

You can use OAuth 2.0 and OIDC without a cloud identity provider by building your own server or by using an open-source implementation. However, wouldn’t you rather just use something that’s always on, like Okta?

If you already have an Okta account, see the Create a Web Application in Okta sidebar below. Otherwise, we created a Maven plugin that configures a free Okta developer account + an OIDC app (in under a minute!).

To use it, add the following plugin repository to your gateway project’s pom.xml:

<pluginRepositories>
    <pluginRepository>
        <id>ossrh</id>
        <releases><enabled>false</enabled></releases>
        <snapshots><enabled>true</enabled></snapshots>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </pluginRepository>
</pluginRepositories>

Then run ./mvnw com.okta:okta-maven-plugin:setup to create an account and configure your Spring Boot app to work with Okta.

Create a Web Application in Okta

Log in to your Okta Developer account (or sign up if you don’t have an account).

  1. From the Applications page, choose Add Application.
  2. On the Create New Application page, select Web.
  3. Give your app a memorable name, add http://localhost:8080/login/oauth2/code/okta as a Login redirect URI, select Refresh Token (in addition to Authorization Code), and click Done.

Copy the issuer (found under API > Authorization Servers), client ID, and client secret into application.properties for both projects.

okta.oauth2.issuer=$issuer
okta.oauth2.client-id=$clientId
okta.oauth2.client-secret=$clientSecret

Next, add the Okta Spring Boot starter and Spring Cloud Security to your gateway’s pom.xml:

<dependency>
   <groupId>com.okta.spring</groupId>
   <artifactId>okta-spring-boot-starter</artifactId>
   <version>1.2.1</version>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-security</artifactId>
</dependency>

This is all you need to do to add OIDC login with Okta! Restart your Gateway app and navigate to http://localhost:8080/fave-cars in your browser to be redirected to Okta for user authorization.

Make Your Gateway an OAuth 2.0 Resource Server

You likely won’t build the UI for your app on the gateway itself. You’ll probably use a SPA or mobile app instead. To configure your gateway to operate as a resource server (that looks for an Authorization header with a bearer token), add a new SecurityConfiguration class in the same directory as your main class.

package com.example.apigateway;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .oauth2Login()
                .and()
            .oauth2ResourceServer()
                .jwt();
        return http.build();
        // @formatter:on
    }
}

CORS with Spring Cloud Gateway

If you’re using a SPA for your UI, you’ll want to configure CORS as well. You can do this by adding a CorsWebFilter bean to this class.

@Bean
CorsWebFilter corsWebFilter() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(List.of(""));
    corsConfig.setMaxAge(3600L);
    corsConfig.addAllowedMethod("
");
    corsConfig.addAllowedHeader("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);
}

Make sure your imports match the ones below.

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

Spring Cloud Gateway’s documentation explains how to configure CORS with YAML or with WebFluxConfigurer. Unfortunately, I was unable to get either one to work.

Test Your Gateway with WebTestClient and JWT

If you configured CORS in your gateway, you can test it works with WebTestClient. Replace the code in ApiGatewayApplicationTests with the following.

package com.example.apigateway;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class ApiGatewayApplicationTests {

    @Autowired
    WebTestClient webTestClient;

    @MockBean
    ReactiveJwtDecoder jwtDecoder;

    @Test
    public void testCorsConfiguration() {
        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
        WebTestClient.ResponseSpec response = webTestClient.put().uri("/")
                .headers(addJwt(jwt))
                .header("Origin", "http://example.com")
                .exchange();

        response.expectHeader().valueEquals("Access-Control-Allow-Origin", "*");
    }

    private Jwt jwt() {
        return new Jwt("token", null, null,
                Map.of("alg", "none"), Map.of("sub", "betsy"));
    }

    private Consumer<HttpHeaders> addJwt(Jwt jwt) {
        return headers -> headers.setBearerAuth(jwt.getTokenValue());
    }
}

  1. Mock ReactiveJwtDecoder so you can set expectations and return mocks when it decodes
  2. Create a new JWT
  3. Return the same JWT when it’s decoded
  4. Add the JWT to the Authorization header with a Bearer prefix

I like how WebTestClient allows you to set the security headers so easily!

You’ve configured Spring Cloud Gateway to use OIDC login and function as an OAuth 2.0 resource server, but the car service is still available on port 8081. Let’s fix that so only the gateway can talk to it.

Secure Gateway to Microservice Communication

Add the Okta Spring Boot starter to car-service/pom.xml:

<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-boot-starter</artifactId>
    <version>1.2.1</version>
</dependency>

Copy the okta.* properties from the gateway’s application.properties to the car service’s. Then create a SecurityConfiguration class to make the app an OAuth 2.0 resource server.

package com.example.carservice;

import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .oauth2ResourceServer()
                .jwt();

        Okta.configureResourceServer401ResponseBody(http);

        return http.build();
        // @formatter:on
    }
}

That’s it! Restart your car service application and it’s now protected from anonymous intruders.

$ http :8081/cars
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: text/plain
...

401 Unauthorized

Test Your Microservice with WebTestClient and JWT

The tests you added in the car-service project will no longer work now that you’ve enabled security. Modify the code in CarServiceApplicationTests.java to add JWT access tokens to each request.

package com.example.carservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class CarServiceApplicationTests {

    @Autowired
    CarRepository carRepository;

    @Autowired
    WebTestClient webTestClient;

    @MockBean
    ReactiveJwtDecoder jwtDecoder;

    @Test
    public void testAddCar() {
        Car buggy = new Car(UUID.randomUUID(), "ID. BUGGY", LocalDate.of(2022, Month.DECEMBER, 1));

        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.post().uri("/cars")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .headers(addJwt(jwt))
                .body(Mono.just(buggy), Car.class)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBody()
                .jsonPath("$.id").isNotEmpty()
                .jsonPath("$.name").isEqualTo("ID. BUGGY");
    }

    @Test
    public void testGetAllCars() {
        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.get().uri("/cars")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .headers(addJwt(jwt))
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBodyList(Car.class);
    }

    @Test
    public void testDeleteCar() {
        Car buzzCargo = carRepository.save(new Car(UUID.randomUUID(), "ID. BUZZ CARGO",
                LocalDate.of(2022, Month.DECEMBER, 2))).block();

        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.delete()
               .uri("/cars/{id}", Map.of("id", buzzCargo.getId()))
                .headers(addJwt(jwt))
                .exchange()
                .expectStatus().isOk();
    }

    private Jwt jwt() {
        return new Jwt("token", null, null,
                Map.of("alg", "none"), Map.of("sub", "dave"));
    }

    private Consumer<HttpHeaders> addJwt(Jwt jwt) {
        return headers -> headers.setBearerAuth(jwt.getTokenValue());
    }
}

Run the test again and everything should pass!

Mock JWT Support in Spring Security 5.2

Kudos to Josh Cummings for his help with JWTs and WebTestClient. Josh gave me a preview of the mock JWT support coming in Spring Security 5.2.

this.webTestClient.mutateWith(jwt()).post(...)

Josh also provided an example test showing how to mock a JWT’s subject, scope, and claims. This code is based on new functionality in Spring Security 5.2.0.M3.

The future is bright for OAuth 2.0 and JWT support in Spring Security land! 😎

Relay the Access Token: Gateway to Microservice

You only need to make one small change for your gateway to talk to this protected service. It’s incredibly easy and I ❤️ it!

In ApiGatewayApplication.java, add a filter that applies the TokenRelayGatewayFilterFactory from Spring Cloud Security.

import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
                                       TokenRelayGatewayFilterFactory filterFactory) {
    return builder.routes()
            .route("car-service", r -> r.path("/cars")
                    .filters(f -> f.filter(filterFactory.apply()))
                    .uri("lb://car-service/cars"))
            .build();
}

This relay factory does not automatically refresh access tokens (yet).

Restart your API gateway and you should be able to view http://localhost:8080/cars and have everything work as expected.

Pretty sweet, don’t you think?!

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about Microservices

An Introduction to Microservices

What is Microservices?

Build Spring Microservices and Dockerize Them for Production

Best Java Microservices Interview Questions In 2019

Build a microservices architecture with Spring Boot and Spring Cloud

Design patterns for microservices 🍂 🍂 🍂

Kotlin Microservices With Micronaut, Spring Cloud, and JPA

Build Spring Microservices and Dockerize Them for Production

Secure Service-to-Service Spring Microservices with HTTPS and OAuth 2.0

Build Secure Microservices with AWS Lambda and ASP.NET Core


Learn Spring Framework and Spring Boot - Build a Spring Web Application

Learn Spring Framework and Spring Boot - Build a Spring Web Application: If you're new to the Spring Framework, this is the course you want to start with. This course covers the core of the Spring Framework, the foundation which all of the other Spring Framework projects are built from.

Build a web application using Spring Framework and *Spring Boot *

If you're new to the Spring Framework, this is the course you want to start with. This course covers the core of the Spring Framework, the foundation which all of the other Spring Framework projects are built from.

In this course, you will learn about important key concepts, such as dependency injection and inversion of control, which are used throughout the Spring Framework. Within the Spring Framework, you have the option of using the traditional XML configuration, or the new Java based configuration. I'll show you step by step how to configure Spring Beans using best practices in XML and Java. I'll also show you how to use Spring to persist data into a database, and Spring MVC to show content from the database on a webpage.

By the time we reach the end of this course, you will be able to build a functioning Spring Web Application.

In this course, you will learn about:

  • Dependency Injection and Inversion of Control (IoC) in the Spring Framework.
  • Spring Boot
  • Using Spring Initializr
  • Using Maven to build Spring Projects
  • How to use JUnit and Mockito to test Spring
  • Java and XML Spring Configuration
  • Spring MVC and Thymeleaf with Bootstrap CSS
  • Spring MVC Test
  • JPA / Hibernate
  • Spring JPA and using DAOs
  • Spring Profiles

Build a Java 8 Spring Boot App with Docker

Build a Java 8 Spring Boot App with Docker

Build a Java 8 Spring Boot App with Docker - In this article, I am going to show you how to develop and run a simple Spring web application using Java 8 without installing Java 8 on your local machine.

Python developers use virtual environments for creating and managing separate environments for different projects, and each using different versions of Python for execution and storing and resolving Python dependencies. Java and many other technologies do not support a virtual environment concept. At this point, Docker comes to our aid.

Docker is a virtualization platform. I am not going dive into details of docker's details. you can find basic information and installation guide from the docker official site.

Once you have the Docker toolbox installed, you do not need install Java 8 or MySQL which are needed in our sample application.

Now, you can download my codes from GitHub.

First, let's check the Docker-compose file:

version : '2'
services:

  springappserver:
    build:
      context: . 
      dockerfile: springapp.dockerfile
    ports: 
      - "8080:8080"
    networks:
      - net-spring-db
    volumes:
      - .:/vol/development
    depends_on:
      - mysqldbserver

  mysqldbserver:
    build:
      context: . 
      dockerfile: mysqldb.dockerfile
    ports:
      - "3306:3306"
    networks:
      - net-spring-db
    environment:
      MYSQL_DATABASE: testdb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
      MYSQL_ROOT_PASSWORD: myrootpassword
    container_name: mysqldbserver

networks:
  net-spring-db:
    driver: bridge

We have two servers each on the 'net-spring-db' network. The first one is named 'springappserver' and configured with the springapp.dockerfile, which will be described later. The second one is named as mysqldbserver and configured with the mysqldb.dockerfile, which will be described later.

Now, let's have a look at the springapp.dockerfile:

#
# Java 1.8 & Maven Dockerfile
#
#

# pull base image.
FROM java:8

# maintainer
MAINTAINER Dursun KOC "[email protected]"

# update packages and install maven
RUN  \
  export DEBIAN_FRONTEND=noninteractive && \
  sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list && \
  apt-get update && \
  apt-get -y upgrade && \
  apt-get install -y vim wget curl maven

# attach volumes
VOLUME /vol/development

# create working directory
RUN mkdir -p /vol/development
WORKDIR /vol/development

# maven exec
CMD ["mvn", "clean", "package", "exec:java"]

This Docker file configures a Docker image, which is inherited from a Java 8 image from Docker Hub. Over that Java 8 image, I have installed vim, wget, curl, Maven, and set the volume in order to put my existing projects code. And finally, execute the Maven command to run my application.

Now let's check the mysqldb.dockerfile:

FROM mysql/mysql-server

MAINTAINER Dursun KOC <[email protected]>

# Copy the database initialize script: 
# Contents of /docker-entrypoint-initdb.d are run on mysqld startup
ADD  mysql/ /docker-entrypoint-initdb.d/

This Dockerfile configures a Docker image, which is inherited from the MySQL/mysql-server image from Docker Hub. Over the MySQL image, I put my db-schema creation scripts, which are located in the MySQL folder. I have a single SQL file at this folder — data.sql — in order to create the 'person' table.

Now, let's see application structure.

Our application is started from the src/com/turkcell/softlab/Application.java file, and our only Controller is the PersonController(src/com/turkcell/softlab/controller/PersonController.java).

You can run the whole project with a simple command:

docker-compose up -d

For testing, use the following two commands in your local machine:

  • Create new person:
curl -H "Content-Type: application/json" -X POST -d "{\"first\": \"Mustafa\",\"last\": \"KOÇ\",\"dateofbirth\": 381110400000,\"placeofbirth\": \"Erzincan\"}" "http://192.168.99.100:8080/people"
  • List existing people in the database:
curl -H "Content-Type: application/json" -X GET "http://192.168.99.100:8080/people"

Now, it's your turn! You can dive deeper into Java 8 and Spring Boot using this template.