Spring 5 : Functional Web Framework | Code Factory


Donate : Link

Medium Blog : Link

Applications : Link

Spring WebFlux framework introduces a new functional web framework built using reactive principles.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>org.example</groupId>
	<artifactId>spring5-functional-web-crud-api</artifactId>
	<version>1.0-SNAPSHOT</version>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>

	<properties>
		<spring.version>5.0.0.RELEASE</spring.version>
	</properties>

	<dependencyManagement>
        <dependencies>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-bom</artifactId>
			<version>Bismuth-RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
		</dependency>
        </dependencies>
	</dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.reactivestreams</groupId>
			<artifactId>reactive-streams</artifactId>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-core</artifactId>
		</dependency>
		<dependency>
			<groupId>io.projectreactor.ipc</groupId>
			<artifactId>reactor-netty</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>8.5.4</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webflux</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.9.1</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
	<Appenders>
		<Console name="Console" target="SYSTEM_OUT">
			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
		</Console>
	</Appenders>
	<Loggers>
		<Logger name="org.springframework" level="info" />
		<Logger name="org.springframework.web" level="debug" />
		<Root level="error">
			<AppenderRef ref="Console" />
		</Root>
	</Loggers>
</Configuration>

Client.java

package com.example;

import java.net.URI;
import java.util.List;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;

import com.example.model.Employee;

/**
 * @author code.factory
 */
public class Client {

	private ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());

	public static void main(String[] args) throws Exception {
		Client client = new Client();
		client.createPerson();
		client.printAllPeople();
	}

	public void printAllPeople() {
		URI uri = URI.create(String.format("http://%s:%d/person", Server.HOST, Server.PORT));
		ClientRequest request = ClientRequest.method(HttpMethod.GET, uri).build();

		Flux<Employee> people = exchange.exchange(request)
				.flatMapMany(response -> response.bodyToFlux(Employee.class));

		Mono<List<Employee>> peopleList = people.collectList();
		System.out.println(peopleList.block());
	}

	public void createPerson() {
		URI uri = URI.create(String.format("http://%s:%d/person", Server.HOST, Server.PORT));
		Employee jack = new Employee("Emp3", 23);

		ClientRequest request = ClientRequest.method(HttpMethod.POST, uri)
				.body(BodyInserters.fromObject(jack)).build();

		Mono<ClientResponse> response = exchange.exchange(request);

		System.out.println(response.block().statusCode());
	}

}

Server.java

package com.example;

import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import reactor.ipc.netty.http.server.HttpServer;

import com.example.controller.EmployeeHandler;
import com.example.repository.EmployeeRepository;
import com.example.repository.IEmployeeRepository;


/**
 * @author code.factory
 */
public class Server {

	public static final String HOST = "localhost";

	public static final int PORT = 8080;

	public static void main(String[] args) throws Exception {
		Server server = new Server();
		server.startReactorServer();
		// server.startTomcatServer();

		System.out.println("Press ENTER to exit.");
		System.in.read();
	}

	public RouterFunction<ServerResponse> routingFunction() {
		IEmployeeRepository repository = new EmployeeRepository();
		EmployeeHandler handler = new EmployeeHandler(repository);

		return route(GET("/person/{id}"), handler::getPerson)
				.and(route(GET("/person"), handler::listPeople))
				.and(route(POST("/person"), handler::createPerson))
				.and(route(PUT("/person/{id}"), handler::updatePerson))
				.and(route(DELETE("/person/{id}"), handler::deletePerson));

	}

	public void startReactorServer() throws InterruptedException {
		RouterFunction<ServerResponse> route = routingFunction();
		HttpHandler httpHandler = toHttpHandler(route);

		ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
		HttpServer server = HttpServer.create(HOST, PORT);
		server.newHandler(adapter).block();
	}

	public void startTomcatServer() throws LifecycleException {
		RouterFunction<?> route = routingFunction();
		HttpHandler httpHandler = toHttpHandler(route);

		Tomcat tomcatServer = new Tomcat();
		tomcatServer.setHostname(HOST);
		tomcatServer.setPort(PORT);
		Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
		ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
		Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
		rootContext.addServletMapping("/", "httpHandlerServlet");
		tomcatServer.start();
	}

}

EmployeeHandler.java

package com.example.controller;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import com.example.model.Employee;
import com.example.repository.IEmployeeRepository;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author code.factory
 *
 */
public class EmployeeHandler {

	private final IEmployeeRepository repository;

	public EmployeeHandler(IEmployeeRepository repository) {
		this.repository = repository;
	}

	public Mono<ServerResponse> getPerson(ServerRequest request) {
		int personId = Integer.valueOf(request.pathVariable("id"));
		Mono<ServerResponse> notFound = ServerResponse.notFound().build();
		Mono<Employee> personMono = this.repository.getPerson(personId);
		return personMono
				.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
				.switchIfEmpty(notFound);
	}

	public Mono<ServerResponse> listPeople(ServerRequest request) {
		Flux<Employee> people = this.repository.allPeople();
		return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Employee.class);
	}

	public Mono<ServerResponse> createPerson(ServerRequest request) {
		Mono<Employee> person = request.bodyToMono(Employee.class);
		return ServerResponse.ok().build(this.repository.savePerson(person));
	}

	public Mono<ServerResponse> updatePerson(ServerRequest request) {
		int personId = Integer.valueOf(request.pathVariable("id"));
		Mono<Employee> person = request.bodyToMono(Employee.class);
		Mono<Employee> personMono = this.repository.getPerson(personId);
		if(personMono.equals(Mono.empty())) {
			return ServerResponse.notFound().build();
		}
		return ServerResponse.ok()
				.contentType(APPLICATION_JSON)
				.body(this.repository.updatePerson(personId, person), Employee.class);
	}

	public Mono<ServerResponse> deletePerson(ServerRequest request) {
		int personId = Integer.valueOf(request.pathVariable("id"));
		Mono<Employee> personMono = this.repository.getPerson(personId);
		if(personMono.equals(Mono.empty())) {
			return ServerResponse.notFound().build();
		}
		return ServerResponse.ok().build(this.repository.deletePerson(personId));
	}
}

Employee.java

package com.example.model;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * @author 
 *
 */
public class Employee {

	private final String name;

	private final int age;

	public Employee(@JsonProperty("name") String name, @JsonProperty("age") int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return this.name;
	}

	public int getAge() {
		return this.age;
	}

	@Override
	public String toString() {
		return "Person{" +
				"name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

EmployeeRepository.java

package com.example.repository;

import java.util.HashMap;
import java.util.Map;

import com.example.model.Employee;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author code.factory
 */
public class EmployeeRepository implements IEmployeeRepository {

	private final Map<Integer, Employee> people = new HashMap<>();

	public EmployeeRepository() {
		this.people.put(1, new Employee("Emp1", 21));
		this.people.put(2, new Employee("Emp2", 22));
	}

	@Override
	public Mono<Employee> getPerson(int id) {
		return Mono.justOrEmpty(this.people.get(id));
	}

	@Override
	public Flux<Employee> allPeople() {
		System.out.println(this.people.toString());
		return Flux.fromIterable(this.people.values());
	}

	@Override
	public Mono<Void> savePerson(Mono<Employee> personMono) {
		return personMono.doOnNext(person -> {
			int id = people.size() + 1;
			people.put(id, person);
			System.out.println("Saved " + person);
		}).thenEmpty(Mono.empty());
	}

	@Override
	public Mono<Employee> updatePerson(int personId, Mono<Employee> personMono) {
		return personMono.doOnNext(person -> {
			people.put(personId, person);
			System.out.println("Updated " + person);
		});
	}

	@Override
	public Mono<Void> deletePerson(int id) {
		this.people.remove(id);
		return Mono.empty();
	}
}

IEmployeeRepository.java

package com.example.repository;

import com.example.model.Employee;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author code.factory
 */
public interface IEmployeeRepository {

	Mono<Employee> getPerson(int id);

	Flux<Employee> allPeople();

	Mono<Void> savePerson(Mono<Employee> person);
	
	Mono<Employee> updatePerson(int id, Mono<Employee> person);
	
	Mono<Void> deletePerson(int id);

}

First run Server.java after that you can check service using 2 ways.
1. Using postman or any other API testing software
2. Run Client.java

Here i test this service using postman

Note : use Content-Type = application/json

HTTP GET http://localhost:8080/person

HTTP GET http://localhost:8080/person/1

HTTP POST http://localhost:8080/person

HTTP PUT http://localhost:8080/person/3

HTTP DELETE http://localhost:8080/person/3

8 thoughts on “Spring 5 : Functional Web Framework | Code Factory”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s