/java-function-invoker

Primary LanguageJavaApache License 2.0Apache-2.0

Java Function Invoker Build Status

What It Does

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.

How To Use It

Function source

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.

Request-reply functions

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);
	}
}

Streaming functions

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) {
        // [...]
    }
}

Function detection

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>

Function handler

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,

Function samples

We have sample application for plain Java function and for Spring Boot app with a function bean.

Cloud Native Buildpacks

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.

How it Works

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.