Spring Cloud Function provides a new programming model for Spring Boot applications, abstracting away all of the transport details and infrastructure, allowing the developer to keep all the familiar tools and processes, and focus firmly on business logic.
Here’s a complete, executable, testable Spring Boot application (implementing a simple string manipulation):
@SpringBootApplication
public class Application {
@Bean
public Function<Flux<String>, Flux<String>> uppercase() {
return flux -> flux.map(value -> value.toUpperCase());
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
It’s just a Spring Boot application, so it can be built, run and
tested, locally and in a CI build, the same way as any other Spring
Boot application. The Function
is from java.util
and Flux
is a
Reactive Streams Publisher
from
Project Reactor. The function can be
accessed over HTTP or messaging.
Spring Cloud Function has 4 main features:
-
Wrappers for
@Beans
of typeFunction
,Consumer
andSupplier
, exposing them to the outside world as either HTTP endpoints and/or message stream listeners/publishers with RabbitMQ, Kafka etc. -
Compiling strings which are Java function bodies into bytecode, and then turning them into
@Beans
that can be wrapped as above. -
Deploying a JAR file containing such an application context with an isolated classloader, so that you can pack them together in a single JVM.
-
TBD: adapters for AWS Lambda, and possibly other "serverless" service providers.
Build from the command line (and "install" the samples):
$ ./mvnw clean install
(If you like to YOLO add -DskipTests
.)
Run one of the samples, e.g.
$ java -jar spring-cloud-function-samples/spring-cloud-function-sample/target/*.jar
This runs the app and exposes its functions over HTTP, so you can convert a string to uppercase, like this:
$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d Hello
HELLO
You can convert multiple strings (a Flux<String>
) by separating them
with new lines
$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d 'Hello
> World'
HELLOWORLD
(You can use QJ
in a terminal to insert a new line in a literal
string like that.)
The sample @SpringBootApplication
above has a function that can be
decorated at runtime by Spring Cloud Function to be an HTTP endpoint,
or a Stream processor, for instance with RabbitMQ, Apache Kafka or
JMS.
The @Beans
can be Function
, Consumer
or Supplier
(all from
java.util
), and their parametric types can be String or POJO. A
Function
is exposed as an HTTP POST if spring-cloud-function-web
is on the classpath, and as a Spring Cloud Stream Processor
if
spring-cloud-function-stream
is on the classpath and a
function.name
is configured in the Spring environment. A Consumer
is also exposed as an HTTP POST, or as a Stream Sink
. A Supplier
translates to an HTTP GET, or a Stream Source
.
Functions can be of Flux<String>
or Flux<Pojo>
and Spring Cloud
Function takes care of converting the data to and from the desired
types, as long as it comes in as plain text or (in the case of the
POJO) JSON. TBD: support for Flux<Message<Pojo>>
and maybe plain
Pojo
types (Fluxes implied and implemented by the framework).
Functions can be grouped together in a single application, or deployed one-per-jar. It’s up to the developer to choose. An app with multiple functions can be deployed multiple times in different "personalities", exposing different functions over different physical transports.
To run these examples, change into the scripts
directory:
cd scripts
Also, start a RabbitMQ server locally (e.g. execute rabbitmq-server
).
./registerFunction.sh -n uppercase -f "f->f.map(s->s.toString().toUpperCase())"
./web.sh -f uppercase -p 9000 curl -H "Content-Type: text/plain" -H "Accept: text/plain" :9000/uppercase -d foo
./web.sh -s words -p 9001 curl -H "Accept: application/json" :9001/words
./web.sh -c print -p 9002 curl -X POST -H "Content-Type: text/plain" -d foo :9002/print
First register a streaming words supplier:
./registerSupplier.sh -n wordstream -f "()->Flux.intervalMillis(1000).map(i->\"message-\"+i)"
Then start the source (supplier), processor (function), and sink (consumer) apps (in reverse order):
./stream.sh -p 9103 -i uppercaseWords -c print ./stream.sh -p 9102 -i words -f uppercase -o uppercaseWords ./stream.sh -p 9101 -s wordstream -o words
The output will appear in the console of the sink app (one message per second, converted to uppercase):
MESSAGE-0 MESSAGE-1 MESSAGE-2 MESSAGE-3 MESSAGE-4 MESSAGE-5 MESSAGE-6 MESSAGE-7 MESSAGE-8 MESSAGE-9 ...