The "Java Function Invoker" lets you concentrate on writing your business logic as a Java function while the invoker takes care of the rest that is needed to run your functions in a Kubernetes cluster with riff installed. The invoker is a Spring Boot application that will locate your function in the JAR file you provide based on some configuration settings. It will then expose the function over gRPC using riff's streaming protocol. When used in a function service like riff, the invoker boot application is provided by the platform when functions are built and basic request/reply http support is added via the streaming/http adapter.
You need to configure a Maven or Gradle project for your function. If you use Spring Boot then we recommend using Spring Initializr to bootstrap your project. If you are not using Spring Boot for your function code then you need to create your own build configuration.
The Spring Cloud Function project provides support for writing functions as part of a Spring Boot app.
The Java Function Invoker does not require any dependencies for simple request-reply functions.
It only requires that your function implements the java.util.function.Function
interface.
Example of a plain Java function:
package functions;
import java.util.function.Function;
public class Upper implements Function<String, String> {
public String apply(String name) {
return name.toUpperCase();
}
}
Example of a Spring Boot app with a function bean:
package com.example.uppercase;
import java.util.function.Function;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class UppercaseApplication {
@Bean
Function<String, String> uppercase() {
return s -> s.toUpperCase();
}
public static void main(String[] args) {
SpringApplication.run(UppercaseApplication.class, args);
}
}
If you want to author a streaming function, you will also need to pull Reactor Core (io.projectreactor:reactor-core
) as the invoker leverages Reactor's Flux
API to work with streams.
package functions;
import reactor.core.publisher.Flux;
import java.util.function.Function;
public class Upper implements Function<Flux<String>, Flux<String>> {
public String apply(Flux<String> names) {
return names.map(String::length);
}
}
If the function accepts several inputs and/or outputs, you can use the Tuple
API of Reactor core. In the following example, the function accepts 2 input streams and 3 output streams:
package io.projectriff.invoker.server;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple3;
import java.util.function.Function;
public class MultiInputOutputFunction implements Function<Tuple2<Flux<String>, Flux<Integer>>,
Tuple3<Flux<String>, Flux<Boolean>, Flux<Integer>>> {
@Override
public Tuple3<Flux<String>, Flux<Boolean>, Flux<Integer>> apply(Tuple2<Flux<String>, Flux<Integer>> objects) {
// [...]
}
}
Spring Cloud Function will attempt to detect the function from the function source.
If you have a single function declared with @Bean
in a Spring Boot app then that is the function that will be used.
If you have multiple functions in the source then you have to specify which one you want using a bean name (see the next section).
If you have a Plain Java class with a function then you can provide a Function-Class
entry in the JAR file manifest
to indicate which function class to use or specify its class explicitly.
Here is an example of using a plug-in for Maven to add this information to the manifest:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Function-Class>functions.Greeter</Function-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
If your function can't be automatically detected then you need to provide a handler specification. The simplest form of the handler is a bean name or a class name that can be instantiated (with a default constructor). More complex creation scenarios can be handled by giving the handler via configuration properties:
spring.cloud.function.location
the file path of a jar file containing the function class, Boot uber-jar or vanilla jar,spring.cloud.function.function-class
is a class name,spring.cloud.function.definition
is a bean name,
We have sample application for plain Java function and for Spring Boot app with a function bean.
Buildpacks provide a higher-level abstraction for building apps compared to Dockerfiles.
The riff project provides its own builder that specifically targets building functions using the riff function invokers.
As long as the dependencies are included in the archive correctly, you can supply a Function
with a wide range of input and output types.
The input or output types can be plain JDK classes, or POJOs defined in your archive, or Message
(from spring-messaging
) or Publisher
(from reactive-streams
) or Flux
or Mono
(from reactor-core
).
Input and output types can also be reactor's TupleX
classes, thus allowing multi I/O functions.
The Message
type will give you access to header metadata in the incoming and outgoing messages.
POJOs are converted from incoming messages / to return values using the Content-Type
header and the expectedContentType
field value.