How Long Will It Take before Java Developers Can Utilize Serverless?

By Fang Jian (luoye), Founder and Head of the Spring Cloud Alibaba Open-Source Project

This article is taken from the book, In-depth Understanding and Practices of Spring Cloud, written by Fang Jian. This article describes how to deal with FaaS scenarios under the Java microservice framework Spring Boot/Cloud.

Serverless and FaaS

Serverless is becoming a mainstream trend. The following figure shows the change from the management of monolithic applications to microservice applications to functions.

So far, Serverless does not have a precise definition. Martin Fowler wrote an article on his blog called Serverless Architectures, which defines Serverless as the combination of BaaS and FaaS.

BaaS is short for Backend as a Service, and FaaS is short for Function as a Service.

Now, let’s talk about FaaS. This is the definition of FaaS from Wikipedia, “Function as a Service (FaaS) is a type of cloud computing service. It provides a platform that allows customers to develop, run, and manage application functions without building and maintaining the infrastructure typically associated with application development and launch. Building applications based on this model is a way to implement Serverless architecture. FaaS is usually used when building microservice applications.”

Python and JavaScript are natively supported in Lambda and can be combined perfectly with FaaS. The Serverless Framework Research Report also supports this combination. Node.js and Python are the top two languages used in FaaS.

Java-based applications start up slowly because JVM consumes a lot of memory. So, FaaS is not suitable for Java-based applications. This is why Java usage in FaaS is low.

Spring Boot/Cloud has become the de facto standard for Java developers. Dependency injection is the core of the Spring Framework. What will happen if Spring Boot/Cloud is used to address FaaS scenarios? This involves the Spring Cloud Function being introduced in this article.

Java Function

JDK 1.8 released a new feature called Lambda expressions. Many functions are provided in the java.util.function package. The following three functions are particularly important:

1. java.util.function.Function: A Parameter Is Required to Obtain Another Result

@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

For example, the map method in Stream API can change an uppercase string to a lowercase one through the Function:

Stream.of("a", "b", "c").map(String::toUpperCase);

The map method requires a Function parameter:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

2. java.util.function.Consumer: A Parameter Is Required for Operations without Returning Any Value

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

For example, the forEach method of Stream API can traverse each element and perform corresponding business logic processing:

RestTemplate restTemplate = new RestTemplate();
Stream.of("200", "201", "202").forEach(code -> {
ResponseEntity<String> responseEntity =
restTemplate.getForEntity("http://httpbin.org/status/" + code, String.class);
System.out.println(responseEntity.getStatusCode());
});

3. java.util.function.Supplier: A Result Is Returned with No Input Parameter

@FunctionalInterface
public interface Supplier<T> {
T get();
}

For example, the custom Supplier can return a random number:

Random random = new Random();Supplier supplier100 = () -> random.nextInt(100);
Supplier supplier1000 = () -> random.nextInt(1000);
System.out.println(supplier100.get());
System.out.println(supplier1000.get());

Spring Cloud Function

  • Supplier
  • Function
  • Consumer

The Spring Cloud Function is a Spring ecosystem project related to Serverless (FaaS). It is designed to enhance the Java Function in the following aspects:

  • Unified FaaS Programming Model of Cloud Vendors: The slogan of Spring Cloud Function is “Write Once, Run Anywhere.” The Spring Cloud Function code can run locally and in various products of cloud vendors, such as AWS Lambda, GCP Cloud Functions, and Azure Functions.
  • Automatic Type Conversion: For those that learned about Spring MVC or Spring Cloud Stream, the HttpMessageConverter or MessageConverter is very familiar. This converter automatically converts the HTTP BODY (or Message Payload), HTTP Query Parameter, and HTTP HEADER (or Message HEADER) to the corresponding POJO. With this feature, no attention is needed to the input parameters and returned values of the function. The original input parameter information can be obtained through String parameters. Moreover, with the POJO parameter called User, the original input parameters can be automatically converted into User objects.
  • Function Combination: Multiple functions can be combined.
  • Function Management: FunctionCatalog and FunctionRegistry interfaces are provided for Function management. They allow Function management in ApplicationContext and dynamic Function Registration.
  • Reactive Support: The Spring Cloud Function provides Reactive functions, such as FluxFunction, FluxSupplier, and FunctionConsumer.

The Spring Cloud Function automatically and deeply integrates with the original components in the Spring ecosystem:

  • Spring Web/Spring WebFlux: An HTTP request is a function call.
  • Spring Cloud Task: One-time Task execution is a one-time function call.
  • Spring Cloud Stream: One-time message consumption/production/conversion is a one-time function call.

The unified FaaS programming model of cloud vendors is explained below to give a better understanding of the Spring Cloud Function.

AWS Lambda was the first cloud vendor to provide the FaaS service. RequestStreamHandler is the interface provided by AWS for Java developers. Use the code below to implement the following interface:

public class HandlerStream implements RequestStreamHandler {
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException
{
...

Azure Functions provides the @HttpTrigger annotation for Java developers:

public class Function {
public String echo(@HttpTrigger(name = "req",
methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
String req, ExecutionContext context) {
...
}
}

As shown above, different codes have to be written for different cloud vendors. This process is very difficult if users want to change the cloud vendor.

Interfaces or annotations provided by AWS, Azure, or GCP have no initialization logic related to the Spring context. If a Spring Boot/Cloud application is migrated to the FaaS platform, modifications like the Spring context initialization logic are required. Spring Cloud Function was created to solve these problems.

Spring Cloud Function Usage

@SpringBootApplication
public class SpringCloudFunctionWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionWebApplication.class, args);
}
@Bean
public Function<String, String> upperCase() {
return s -> s.toUpperCase();
}
@Bean
public Function<User, String> user() {
return user -> user.toString();
}
}

Access the corresponding Endpoint:

$ curl -XPOST -H "Content-Type: text/plain" localhost:8080/upperCase -d hello
HELLO
$ curl -XPOST -H "Content-Type: text/plain" localhost:8080/user -d '{"name":"hello SCF"}'
User{name\u003d\u0027hello SCF\u0027}

The Spring Cloud Function and Spring Cloud Stream:

@SpringBootApplication
public class SpringCloudFunctionStreamApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionStreamApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
return x -> x.toUpperCase();
}
@Bean
public Function<String, String> prefix() {
return x -> "prefix-" + x;
}
}

Add the function-related configurations. For each message in an input-topic, the payload is converted into uppercase type and mounted with a prefix of prefix-. Then, the payload is written to the output-topic. For example:

spring.cloud.stream.bindings.input.destination=input-topic
spring.cloud.stream.bindings.input.group=scf-group
spring.cloud.stream.bindings.output.destination=output-topicspring.cloud.stream.function.definition=uppercase|prefix

Spring Cloud Function and Spring Cloud Task:

@SpringBootApplication
public class SpringCloudFunctionTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionTaskApplication.class, args);
}
@Bean
public Supplier<List<String>> supplier() {
return () -> Arrays.asList("200", "201", "202");
}
@Bean
public Function<List<String>, List<String>> function() {
return (list) ->
list.stream().map( item -> "prefix-" + item).collect(Collectors.toList());
}
@Bean
public Consumer<List<String>> consumer() {
return (list) -> {
list.stream().forEach(System.out::println);
};
}
}

Add function-related configurations. The Supplier simulates the job input source. The Function simulates the processing of the job input source, and the Consumer simulates the processing of data processed by the Function. For example:

spring.cloud.function.task.function=function
spring.cloud.function.task.supplier=supplier
spring.cloud.function.task.consumer=consumer

About the Author

Original Source:

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.