The finest-grained application with Spring Cloud Function

After defining an HTTP endpoint, we have to validate the incoming message. Unfortunately, this part of the process holds actual business logic and requires a custom implementation of a stage of the flow. Fortunately, though, Spring Cloud Data Flow allows using custom Spring Cloud Stream applications as part of the process.

We will not go into detail about the implementation of the custom Spring Cloud Data Flow stage here. However, to learn more about the creation and registration of custom Spring Boot applications, follow these links:

On the one hand, we can provide our own separate Spring Cloud Stream application with a custom validation logic. But, on the other hand, we still have to deal with all configurations, uber-jars, long startup time, and the rest of the problems related to the application deployment. Fortunately, those problems can be avoided with the Spring Cloud Function project.

The main aim of Spring Cloud Function is to promote business logic via functions. This project offers the ability to decouple custom business logic from the specifics of runtime. Hence, the same function might be reused in different ways and places.

To learn more about the features of the Spring Cloud Function project, please visit the following link: https://github.com/spring-cloud/spring-cloud-function.

Before we start working with Spring Cloud Function, we are going to cover the principal features of the Spring Cloud Function module in this section and get a better understanding of its internals.

At its core, Spring Cloud Function is an additional level of abstraction for applications that may be running on top of Spring Cloud Streams, AWS Lambda, or any other cloud platform using any communication transport.

By default, Spring Cloud Function has adapters for the configurable deployment of functions to AWS Lambda, Azure Functions, and Apache OpenWhisk. The main benefit of using Spring Cloud Function, as opposed to direct Java function's upload, is the possibility to use most Spring features, and not to depend on a particular cloud provider SDK.

The programming model offered by Spring Cloud Function is nothing more than a definition of one of the following Java classes—java.utils.function.Function, java.utils.function.Supplier, and java.utils.function.Consume. Furthermore, Spring Cloud Function can be used in different framework combinations. For example, we can create a Spring Boot application, which can be a platform for an element of the functions. In turn, some of them may be represented as an ordinary Spring @Bean:

@SpringBootApplication                                             // (1)
@EnableBinding(Processor.class) // (1.1)
public class Application { //

@Bean // (2)
public Function< //
Flux<Payment>, //
Flux<Payment> //
> validate() { //
return flux -> flux.map(value -> { ... }); // (2.1)
} //

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

In the preceding code, the numbered points mean the following:

  1. This is the @SpringBootApplication declaration. As it might be noticed, we still have to define a minimal declaration for a Spring Boot application. Also, we use the @EnableBinding annotation with the Processor interface as a parameter. In such a combination, Spring Cloud identifies a bean at line (2) to be used as a message handler. Also, the input and output of the function are bound to the external destinations exposed by the Processor binding.
  2. This shows Function, which transforms Flux into another Flux as a component of the IoC container. Here, at the point (2.1), we declare a lambda for elements validation, which in turn is a higher-order function that accepts a stream and returns another.
  3. This is the main method declaration, which is used to bootstrap a Spring Boot application.

As may be noticed from the preceding example, Spring Cloud Function supports different programming models. For example, message transformation with the support of Reactor 3 reactive types and Spring Cloud Streams, which exposes those streams to external destinations.

Furthermore, Spring Cloud Function is not limited to predefined functions. For example, it has a built-in runtime compiler that makes it possible to provide a function as a string in the properties' file, as shown in the following example:

spring.cloud.function:                                             // (1)
compile: // (2)
payments: // (3)
type: supplier // (4)
lambda: ()->Flux.just(new org.TestPayment()) // (5)

Each section of the numbered code is explained as follows:

  1. This is the Spring Cloud Function properties namespace.
  2. This is the namespace related to the runtime (on-the-fly) function compilation.
  3. This is the definition of the key, which is the name of the function that is visible inside Spring IoC container. This plays the role of the file name for the compiled Java bytecode.
  4. This is the definition of a type of function. The available options are supplier/function/consumer.
  5. This is the lambda definition. As we can see, the supplier is defined as a String that is compiled into bytecode and stored in the filesystem. The compilation is possible with the support of the spring-cloud-function-compiler module. This has a built-in compiler as well as making it possible to store compiled functions as bytecode and adding them to ClassLoad

The preceding example shows that Spring Cloud Function offers the ability to dynamically define and run functions without also having to have them pre-compiled. Such an ability may be used to implement Function as a Service (FaaS) capabilities in our software solution.

Along with that, Spring Cloud Function provides a module called spring-cloud-function-task, which allows running the mentioned functions in a pipe using the same properties file:

spring.cloud.function:
task: // (1)
supplier: payments // (2)
function: validate|process // (3)
consumer: print // (4)
compile:
print:
type: consumer
lambda: System.out::println
inputType: Object
process:
type: function
lambda: (flux)->flux
inputType: Flux<org.rpis5.chapters.chapter_08.scf.Payment>
outputType: Flux<org.rpis5.chapters.chapter_08.scf.Payment>

The numeration in the code is explained as follows:

  1. This is the namespace used for task configurations. 
  2. Here, we configure a supplier (source) function for the task. As we can see, to define a supplier we have to pass the name of the supplier's function.
  3. These are the intermediate transformations of the data. Here, to pipe the execution, we may combine several functions using the (pipe) | symbol. Note that, under the hood, all functions are chained using the Function#accept method.
  4. This is the definition of the consumer stage. Note that the task is executed only when all stages are provided.

As we can see, by using a pure Spring Boot application with Spring Cloud Function modules as dependencies, it is possible to run users' prepared functions and combine them in a complex handler.

An important role in the Spring Cloud Function ecosystem is played by the spring-cloud-function-compiler module. Along with on-the-fly function compilation from a property file, this module exposes web endpoints, which allow on-the-fly function deployment.  For example, by calling the following curl command in a terminal, we can add the provided function to the running Spring Boot application:

curl -X POST -H "Content-Type: text/plain" 
-d "f->f.map(s->s.toUpperCase())"
localhost:8080/function/uppercase
?inputType=Flux%3CString%3E
&outputTupe=Flux%3CString%3E

In this example, we upload a function with Flux<String> as the input and output type for it.

Note, that we use the %3C %3E symbols here to encode < > in HTTP URI.
There are two options for running spring-cloud-function-compiler as a server: 
  • Download a JAR file from maven-central and run it independently
  • Add the module as a dependency to the project and provide the following path to scan for beans: "org.springframework.cloud.function.compiler.app"

Following a function's deployment by running a lightweight Spring Boot application with dependencies on spring-cloud-function-web and spring-cloud-function-compilerwe obtain on-the-fly function deployment over HTTP and its dynamic deployment as a separate web application. For example, by having the same jar file and changing program arguments, we can run it with different functions as follows:

java -jar function-web-template.jar 
--spring.cloud.function.imports.uppercase.type=function
--spring.cloud.function.imports.uppercase.location=
file:///tmp/function-registry/functions/uppercase.fun

--spring.cloud.function.imports.worldadder.type=function
--spring.cloud.function.imports.worldadder.location=
file:///tmp/function-registry/functions/worldadder.fun

In this example, we import two functions:

  • Uppercase: Which transforms any given string into an uppercase equivalent
  • Worldadder: Which adds the world suffix to any given string

As may be noticed from the previous example code, we use the spring.cloud.function.imports namespace to define the name of the imported functions (in bold) and then their types (in italic) and locations of the bytecode for those functions. After the successful application's startup, we can access deployed functions by executing the following curl command:

curl -X POST -H "Content-Type: text/plain" 
-d "Hello"
localhost:8080/uppercase%7Cworldadder

As the result of the execution, we receive "HELLO World", which ensures that both functions are present on the server and executed in the order defined in the URL.

We use the %7C symbol here to encode (pipe) | in HTTP URI.

In the same way, we may deploy and import other functions within the same or independent applications.

Alternatively, Spring Cloud Function offers a deployment module that plays the role of the container for independent functions. In the previous cases, we were able to run built-in functions or deploy over a spring-cloud-function-compiler web API. We have seen how to use deployed functions and run them as independent applications. Despite that flexibility, the startup time of a Spring Boot application may be much longer than the execution of that function. In some cases (along with the pure functions), we have to use some of the Spring Framework arsenal. For example, relying on Spring Data or Spring Web features. Hence, what could be useful in such cases is the deployment of thin jars. Spring Cloud Function offers an additional module here called spring-cloud-function-deployer.

The Spring Cloud Function Deployer module allows running each jar with the same Spring Deployer application but in complete isolation. At first glance, valuable benefits are not received from using that module. However, as we may remember, independent functions (which are what we want to achieve) are fast in their bootstrapping and execution. A function packed into the Spring Boot environment for its startup requires the startup of the whole Spring Boot application, which usually takes a significant amount of time in comparison to the function's startup time.

Consequently, to solve that problem, Spring Cloud Function Deployer starts itself first and pre-loads some part of the JDK classes. It then creates child ClassLoader for each jar with functions. The execution of each jar takes place in its own Thread which enables parallel execution. Since each jar is an independent micro Spring Boot application, it runs within its own Spring Context, so beans do not intersect with neighboring applications' beans. Finally, the startup of a child Spring Boot application is significantly faster because the parent ClassLoader has already done the difficult job of warming up the JVM.

Moreover, the killer combination of spring-cloud-function-deployer and spring-boot-thin-launcher makes it possible to solve the fat jar problem as well. Spring Boot Thin Launcher is a plugin for Maven and Gradle, which overrides the default Spring Boot fat JarLauncher and offers ThinJarWrapper and ThinJarLauncher instead. Those classes do all of the work required for packaging a dependency-free jar first and then—only during the bootstrap phase—they locate all required dependencies from the configured cache (for example, from the local Maven repo) or download missing dependencies from the configured Maven repositories. Behaving in that way, our application may reduce the size of the jar to a few KB and startup time to hundreds of milliseconds.

To summarize the information gained about Spring Cloud Function, let's take a look at the following generalized diagram of the ecosystem:

Diagram 8.11. The  Spring Cloud Function's ecosystem

In the preceding diagram, the numbered points mean the following:

  1. This is the representation of a function in the form of hexagons. As we can see, there are a few different types of hexagons depicted here. A few of them are a combination of functions within the Spring Boot application, or functions which are exposed over HTTP. Another can communicate with other functions with the support of a Spring Cloud Stream Adapter or can be deployed as a task for single execution.
  2. This is the representation of a Spring Cloud Function Deployer. As was mentioned earlier, the Spring Cloud Function Deployer is depicted as a container. In this case, we have two independent Spring Cloud Function Deployers executed on the different nodes. Also, the dotted borders around functions inside the containers represent independent ClassLoaders.
  3. This is the representation of the Spring Cloud Function Compiler module. In this case, the module plays the role of the server, which allows functions' deployment over HTTP and keeps them in storage.
  4. This is the representation of a message broker, which in this case is RabbitMQ.

As we can see, using Spring Cloud Function modules along with straightforward integration with existing Cloud platforms, we can build our own Function as a Service (FaaS) platform that almost offers the whole arsenal of Spring Framework features, allowing the building of an application using lightweight functions. However, we have to remember that Spring Cloud Function shows the power when there is a foundation for instances' deploying, monitoring and management, so the Spring Cloud Function ecosystem can be built on top of that and exposes FaaS capabilities. Hence, in the following section, we are going to cover how Spring Cloud Function works in combination with the full-fledged Spring Cloud Data Flow ecosystem.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.36.194