Migrate Structure
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
:branch: master
|
||||
|
||||
image::https://travis-ci.org/spring-cloud/spring-cloud-function.svg?branch={branch}[Build Status, link=https://travis-ci.org/spring-cloud/spring-cloud-function]
|
||||
|
||||
== Introduction
|
||||
|
||||
include::_intro.adoc[]
|
||||
|
||||
== Getting Started
|
||||
|
||||
include::getting-started.adoc[]
|
||||
|
||||
== Building
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[]
|
||||
|
||||
== Contributing
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
|
||||
@@ -1,47 +0,0 @@
|
||||
Spring Cloud Function is a project with the following high-level goals:
|
||||
|
||||
* Promote the implementation of business logic via functions.
|
||||
* Decouple the development lifecycle of business logic from any specific runtime target so that the same code can run as a web endpoint, a stream processor, or a task.
|
||||
* Support a uniform programming model across serverless providers, as well as the ability to run standalone (locally or in a PaaS).
|
||||
* Enable Spring Boot features (auto-configuration, dependency injection, metrics) on serverless providers.
|
||||
|
||||
It abstracts 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):
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() {
|
||||
return 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
|
||||
https://www.reactive-streams.org/[Reactive Streams] `Publisher` from
|
||||
https://projectreactor.io/[Project Reactor]. The function can be
|
||||
accessed over HTTP or messaging.
|
||||
|
||||
Spring Cloud Function has the following features:
|
||||
|
||||
* _Choice of programming styles - reactive, imperative or hybrid._
|
||||
* _Function composition and adaptation (e.g., composing imperative functions with reactive)._
|
||||
* _Support for reactive function with multiple inputs and outputs allowing merging, joining and other complex streaming operation to be handled by functions._
|
||||
* _Transparent type conversion of inputs and outputs._
|
||||
* _Packaging functions for deployments, specific to the target platform (e.g., Project Riff, AWS Lambda and more)_
|
||||
* _Adapters to expose function to the outside world as HTTP endpoints etc._
|
||||
* _Deploying a JAR file containing such an application context with an isolated classloader, so that you can pack them together in a single JVM._
|
||||
* _Adapters for https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-aws[AWS Lambda], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[Azure], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp[Google Cloud Functions], and possibly other "serverless" service providers._
|
||||
@@ -1,318 +0,0 @@
|
||||
:branch: master
|
||||
|
||||
=== AWS Lambda
|
||||
|
||||
The https://aws.amazon.com/[AWS] adapter takes a Spring Cloud Function app and converts it to a form that can run in AWS Lambda.
|
||||
|
||||
The details of how to get stared with AWS Lambda is out of scope of this document, so the expectation is that user has some familiarity with
|
||||
AWS and AWS Lambda and wants to learn what additional value spring provides.
|
||||
|
||||
==== Getting Started
|
||||
|
||||
One of the goals of Spring Cloud Function framework is to provide necessary infrastructure elements to enable a _simple function application_
|
||||
to interact in a certain way in a particular environment.
|
||||
A simple function application (in context or Spring) is an application that contains beans of type Supplier, Function or Consumer.
|
||||
So, with AWS it means that a simple function bean should somehow be recognised and executed in AWS Lambda environment.
|
||||
|
||||
Let’s look at the example:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class FunctionConfiguration {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FunctionConfiguration.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() {
|
||||
return value -> value.toUpperCase();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
It shows a complete Spring Boot application with a function bean defined in it. What’s interesting is that on the surface this is just
|
||||
another boot app, but in the context of AWS Adapter it is also a perfectly valid AWS Lambda application. No other code or configuration
|
||||
is required. All you need to do is package it and deploy it, so let’s look how we can do that.
|
||||
|
||||
To make things simpler we’ve provided a sample project ready to be built and deployed and you can access it
|
||||
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws[here].
|
||||
|
||||
You simply execute `./mvnw clean package` to generate JAR file. All the necessary maven plugins have already been setup to generate
|
||||
appropriate AWS deployable JAR file. (You can read more details about JAR layout in <<Notes on JAR Layout>>).
|
||||
|
||||
Then you have to upload the JAR file (via AWS dashboard or AWS CLI) to AWS.
|
||||
|
||||
When ask about _handler_ you specify `org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest` which is a generic request handler.
|
||||
|
||||
image::{github-raw}/docs/src/main/asciidoc/images/AWS-deploy.png[width=800,scaledwidth="75%",align="center"]
|
||||
|
||||
That is all. Save and execute the function with some sample data which for this function is expected to be a
|
||||
String which function will uppercase and return back.
|
||||
|
||||
While `org.springframework.cloud.function.adapter.aws.FunctionInvoker` is a general purpose AWS's `RequestHandler` implementation aimed at completely
|
||||
isolating you from the specifics of AWS Lambda API, for some cases you may want to specify which specific AWS's `RequestHandler` you want
|
||||
to use. The next section will explain you how you can accomplish just that.
|
||||
|
||||
|
||||
==== AWS Request Handlers
|
||||
|
||||
The adapter has a couple of generic request handlers that you can use. The most generic is (and the one we used in the Getting Started section)
|
||||
is `org.springframework.cloud.function.adapter.aws.FunctionInvoker` which is the implementation of AWS's `RequestStreamHandler`.
|
||||
User doesn't need to do anything other then specify it as 'handler' on AWS dashboard when deploying function.
|
||||
It will handle most of the case including Kinesis, streaming etc. .
|
||||
|
||||
|
||||
If your app has more than one `@Bean` of type `Function` etc. then you can choose the one to use by configuring `spring.cloud.function.definition`
|
||||
property or environment variable. The functions are extracted from the Spring Cloud `FunctionCatalog`. In the event you don't specify `spring.cloud.function.definition`
|
||||
the framework will attempt to find a default following the search order where it searches first for `Function` then `Consumer` and finally `Supplier`).
|
||||
|
||||
|
||||
==== AWS Function Routing
|
||||
|
||||
One of the core features of Spring Cloud Function is https://docs.spring.io/spring-cloud-function/docs/{project-version}/reference/html/spring-cloud-function.html#_function_routing_and_filtering[routing]
|
||||
- an ability to have one special function to delegate to other functions based on the user provided routing instructions.
|
||||
|
||||
In AWS Lambda environment this feature provides one additional benefit, as it allows you to bind a single function (Routing Function)
|
||||
as AWS Lambda and thus a single HTTP endpoint for API Gateway. So in the end you only manage one function and one endpoint, while benefiting
|
||||
from many function that can be part of your application.
|
||||
|
||||
More details are available in the provided https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing[sample],
|
||||
yet few general things worth mentioning.
|
||||
|
||||
Routing capabilities will be enabled by default whenever there is more then one function in your application as `org.springframework.cloud.function.adapter.aws.FunctionInvoker`
|
||||
can not determine which function to bind as AWS Lambda, so it defaults to `RoutingFunction`.
|
||||
This means that all you need to do is provide routing instructions which you can do https://docs.spring.io/spring-cloud-function/docs/{project-version}/reference/html/spring-cloud-function.html#_function_routing_and_filtering[using several mechanisms]
|
||||
(see https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing[sample] for more details).
|
||||
|
||||
Also, note that since AWS does not allow dots `.` and/or hyphens`-` in the name of the environment variable, you can benefit from boot support and simply substitute
|
||||
dots with underscores and hyphens with camel case. So for example `spring.cloud.function.definition` becomes `spring_cloud_function_definition`
|
||||
and `spring.cloud.function.routing-expression` becomes `spring_cloud_function_routingExpression`.
|
||||
|
||||
===== AWS Function Routing with Custom Runtime
|
||||
|
||||
When using <<Custom Runtime>> Function Routing works the same way. All you need is to specify `functionRouter` as AWS Handler the same way you would use the name of the function as handler.
|
||||
|
||||
==== Notes on JAR Layout
|
||||
|
||||
You don't need the Spring Cloud Function Web or Stream adapter at runtime in Lambda, so you might
|
||||
need to exclude those before you create the JAR you send to AWS. A Lambda application has to be
|
||||
shaded, but a Spring Boot standalone application does not, so you can run the same app using 2
|
||||
separate jars (as per the sample). The sample app creates 2 jar files, one with an `aws`
|
||||
classifier for deploying in Lambda, and one [[thin-jar,thin jar]] executable (thin) jar that includes `spring-cloud-function-web`
|
||||
at runtime. Spring Cloud Function will try and locate a "main class" for you from the JAR file
|
||||
manifest, using the `Start-Class` attribute (which will be added for you by the Spring Boot
|
||||
tooling if you use the starter parent). If there is no `Start-Class` in your manifest you can
|
||||
use an environment variable or system property `MAIN_CLASS` when you deploy the function to AWS.
|
||||
|
||||
If you are not using the functional bean definitions but relying on Spring Boot's auto-configuration,
|
||||
and are not depending on `spring-boot-starter-parent`,
|
||||
then additional transformers must be configured as part of the maven-shade-plugin execution.
|
||||
|
||||
[[shade-plugin-setup]]
|
||||
[source, xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.7.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>aws</shadedClassifierName>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring.handlers</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
|
||||
<resource>META-INF/spring.factories</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring.schemas</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring.components</resource>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
==== Build file setup
|
||||
|
||||
In order to run Spring Cloud Function applications on AWS Lambda, you can leverage Maven or Gradle
|
||||
plugins offered by the cloud platform provider.
|
||||
|
||||
|
||||
===== Maven
|
||||
|
||||
In order to use the adapter plugin for Maven, add the plugin dependency to your `pom.xml`
|
||||
file:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-aws</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
As pointed out in the <<Notes on JAR Layout>>, you will need a shaded jar in order to upload it
|
||||
to AWS Lambda. You can use the https://maven.apache.org/plugins/maven-shade-plugin/[Maven Shade Plugin] for that.
|
||||
The example of the <<shade-plugin-setup,setup>> can be found above.
|
||||
|
||||
You can use the Spring Boot Maven Plugin to generate the <<thin-jar>>.
|
||||
[source,xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot.experimental</groupId>
|
||||
<artifactId>spring-boot-thin-layout</artifactId>
|
||||
<version>${wrapper.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
You can find the entire sample `pom.xml` file for deploying Spring Cloud Function
|
||||
applications to AWS Lambda with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-sample-aws/pom.xml[here].
|
||||
|
||||
===== Gradle
|
||||
|
||||
In order to use the adapter plugin for Gradle, add the dependency to your `build.gradle` file:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
|
||||
dependencies {
|
||||
compile("org.springframework.cloud:spring-cloud-function-adapter-aws:${version}")
|
||||
}
|
||||
----
|
||||
|
||||
As pointed out in <<Notes on JAR Layout>>, you will need a shaded jar in order to upload it
|
||||
to AWS Lambda. You can use the https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow/[Gradle Shadow Plugin] for that:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "com.github.jengelman.gradle.plugins:shadow:${shadowPluginVersion}"
|
||||
}
|
||||
}
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
assemble.dependsOn = [shadowJar]
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.transformers.*
|
||||
|
||||
shadowJar {
|
||||
classifier = 'aws'
|
||||
dependencies {
|
||||
exclude(
|
||||
dependency("org.springframework.cloud:spring-cloud-function-web:${springCloudFunctionVersion}"))
|
||||
}
|
||||
// Required for Spring
|
||||
mergeServiceFiles()
|
||||
append 'META-INF/spring.handlers'
|
||||
append 'META-INF/spring.schemas'
|
||||
append 'META-INF/spring.tooling'
|
||||
append 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
|
||||
append 'META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports'
|
||||
transform(PropertiesFileTransformer) {
|
||||
paths = ['META-INF/spring.factories']
|
||||
mergeStrategy = "append"
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
You can use the Spring Boot Gradle Plugin and Spring Boot Thin Gradle Plugin to generate
|
||||
the <<thin-jar>>.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${wrapperVersion}")
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
}
|
||||
}
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'org.springframework.boot.experimental.thin-launcher'
|
||||
assemble.dependsOn = [thinJar]
|
||||
----
|
||||
|
||||
You can find the entire sample `build.gradle` file for deploying Spring Cloud Function
|
||||
applications to AWS Lambda with Gradle https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-sample-aws/build.gradle[here].
|
||||
|
||||
==== Upload
|
||||
|
||||
Build the sample under `spring-cloud-function-samples/function-sample-aws` and upload the `-aws` jar file to Lambda. The handler can be `example.Handler` or `org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler` (FQN of the class, _not_ a method reference, although Lambda does accept method references).
|
||||
|
||||
----
|
||||
./mvnw -U clean package
|
||||
----
|
||||
|
||||
Using the AWS command line tools it looks like this:
|
||||
|
||||
----
|
||||
aws lambda create-function --function-name Uppercase --role arn:aws:iam::[USERID]:role/service-role/[ROLE] --zip-file fileb://function-sample-aws/target/function-sample-aws-2.0.0.BUILD-SNAPSHOT-aws.jar --handler org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler --description "Spring Cloud Function Adapter Example" --runtime java8 --region us-east-1 --timeout 30 --memory-size 1024 --publish
|
||||
----
|
||||
|
||||
The input type for the function in the AWS sample is a Foo with a single property called "value". So you would need this to test it:
|
||||
|
||||
----
|
||||
{
|
||||
"value": "test"
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: The AWS sample app is written in the "functional" style (as an `ApplicationContextInitializer`). This is much faster on startup in Lambda than the traditional `@Bean` style, so if you don't need `@Beans` (or `@EnableAutoConfiguration`) it's a good choice. Warm starts are not affected.
|
||||
|
||||
|
||||
==== Type Conversion
|
||||
|
||||
Spring Cloud Function will attempt to transparently handle type conversion between the raw
|
||||
input stream and types declared by your function.
|
||||
|
||||
For example, if your function signature is as such `Function<Foo, Bar>` we will attempt to convert
|
||||
incoming stream event to an instance of `Foo`.
|
||||
|
||||
In the event type is not known or can not be determined (e.g., `Function<?, ?>`) we will attempt to
|
||||
convert an incoming stream event to a generic `Map`.
|
||||
|
||||
====== Raw Input
|
||||
|
||||
There are times when you may want to have access to a raw input. In this case all you need is to declare your
|
||||
function signature to accept `InputStream`. For example, `Function<InputStream, ?>`. In this case
|
||||
we will not attempt any conversion and will pass the raw input directly to a function.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
*{project-version}*
|
||||
|
||||
|
||||
The https://aws.amazon.com/[AWS] adapter takes a Spring Cloud Function app and converts it to a form that can run in AWS Lambda.
|
||||
|
||||
== Introduction
|
||||
|
||||
include::adapters/aws-intro.adoc[]
|
||||
|
||||
== Functional Bean Definitions
|
||||
|
||||
Your functions will start much quicker if you can use functional bean definitions instead of `@Bean`. To do this make your main class
|
||||
an `ApplicationContextInitializer<GenericApplicationContext>` and use the `registerBean()` methods in `GenericApplicationContext` to
|
||||
create all the beans you need. You function need to be registered as a bean of type `FunctionRegistration` so that the input and
|
||||
output types can be accessed by the framework. There is an example in github (the AWS sample is written in this style). It would
|
||||
look something like this:
|
||||
|
||||
```java
|
||||
@SpringBootConfiguration
|
||||
public class FuncApplication implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
FunctionalSpringApplication.run(FuncApplication.class, args);
|
||||
}
|
||||
|
||||
public Function<Foo, Bar> function() {
|
||||
return value -> new Bar(value.uppercase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
context.registerBean("function", FunctionRegistration.class,
|
||||
() -> new FunctionRegistration<Function<Foo, Bar>>(function())
|
||||
.type(FunctionTypeUtils.functionType(Foo.class, Bar.class)));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
== AWS Context
|
||||
|
||||
In a typical implementation of AWS Handler user has access to AWS _context_ object. With function approach you can have the same experience if you need it.
|
||||
Upon each invocation the framework will add `aws-context` message header containing the AWS _context_ instance for that particular invocation. So if you need to access it
|
||||
you can simply have `Message<YourPojo>` as an input parameter to your function and then access `aws-context` from message headers.
|
||||
For convenience we provide AWSLambdaUtils.AWS_CONTEXT constant.
|
||||
|
||||
== Platform Specific Features
|
||||
|
||||
=== HTTP and API Gateway
|
||||
|
||||
AWS has some platform-specific data types, including batching of messages, which is much more efficient than processing each one individually. To make use of these types you can write a function that depends on those types. Or you can rely on Spring to extract the data from the AWS types and convert it to a Spring `Message`. To do this you tell AWS that the function is of a specific generic handler type (depending on the AWS service) and provide a bean of type `Function<Message<S>,Message<T>>`, where `S` and `T` are your business data types. If there is more than one bean of type `Function` you may also need to configure the Spring Boot property `function.name` to be the name of the target bean (e.g. use `FUNCTION_NAME` as an environment variable).
|
||||
|
||||
The supported AWS services and generic handler types are listed below:
|
||||
|
||||
|===
|
||||
| Service | AWS Types | Generic Handler |
|
||||
|
||||
| API Gateway | `APIGatewayProxyRequestEvent`, `APIGatewayProxyResponseEvent` | `org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler` |
|
||||
| Kinesis | KinesisEvent | org.springframework.cloud.function.adapter.aws.SpringBootKinesisEventHandler |
|
||||
|===
|
||||
|
||||
|
||||
For example, to deploy behind an API Gateway, use `--handler org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler` in your AWS command line (in via the UI) and define a `@Bean` of type `Function<Message<Foo>,Message<Bar>>` where `Foo` and `Bar` are POJO types (the data will be marshalled and unmarshalled by AWS using Jackson).
|
||||
|
||||
== Custom Runtime
|
||||
|
||||
You can also benefit from https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html[AWS Lambda custom runtime] feature of AWS Lambda
|
||||
and Spring Cloud Function provides all the necessary components to make it easy.
|
||||
|
||||
From the code perspective the application should look no different then any other Spring Cloud Function application.
|
||||
The only thing you need to do is to provide a `bootstrap` script in the root of your zip/jar that runs the Spring Boot application.
|
||||
and select "Custom Runtime" when creating a function in AWS.
|
||||
Here is an example 'bootstrap' file:
|
||||
```text
|
||||
#!/bin/sh
|
||||
|
||||
cd ${LAMBDA_TASK_ROOT:-.}
|
||||
|
||||
java -Dspring.main.web-application-type=none -Dspring.jmx.enabled=false \
|
||||
-noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \
|
||||
-Djava.security.egd=file:/dev/./urandom \
|
||||
-cp .:`echo lib/*.jar | tr ' ' :` com.example.LambdaApplication
|
||||
```
|
||||
The `com.example.LambdaApplication` represents your application which contains function beans.
|
||||
|
||||
Set the handler name in AWS to the name of your function. You can use function composition here as well (e.g., `uppecrase|reverse`).
|
||||
That is pretty much all. Once you upload your zip/jar to AWS your function will run in custom runtime.
|
||||
We provide a https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws-custom-new[sample project]
|
||||
where you can also see how to configure yoru POM to properly generate the zip file.
|
||||
|
||||
The functional bean definition style works for custom runtimes as well, and is
|
||||
faster than the `@Bean` style. A custom runtime can start up much quicker even than a functional bean implementation
|
||||
of a Java lambda - it depends mostly on the number of classes you need to load at runtime.
|
||||
Spring doesn't do very much here, so you can reduce the cold start time by only using primitive types in your function, for instance,
|
||||
and not doing any work in custom `@PostConstruct` initializers.
|
||||
@@ -1,533 +0,0 @@
|
||||
:branch: master
|
||||
|
||||
== Microsoft Azure Functions
|
||||
:sectnums:
|
||||
|
||||
https://azure.microsoft.com[Azure] function adapter for deploying `Spring Cloud Function` applications as native Azure Java Functions.
|
||||
|
||||
The `Azure Functions` https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java[programming model] relays, extensively, on Java https://learn.microsoft.com/en-us/java/api/com.microsoft.azure.functions.annotation?view=azure-java-stable[annotations] for defining the function's handler methods and their input and output types.
|
||||
At compile time the annotated classes are processed by the provided Azure Maven/Gradle plugins to generate the necessary Azure Function binding files, configurations and package artifacts.
|
||||
The Azure annotations are just a type-safe way to configure your java function to be recognized as Azure function.
|
||||
|
||||
The https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[spring-cloud-function-adapter-azure] extends the basic programming model to provide Spring and Spring Cloud Function support.
|
||||
With the adapter you can build your Spring Cloud Function application using dependency injections and then auto-wire the necessary services into your Azure handler methods.
|
||||
|
||||
image::../images/scf-azure-adapter.svg[width=800,scaledwidth="75%",align="center"]
|
||||
|
||||
TIP: For Web-based function applications, you can replace the generic `adapter-azure` with the specialized https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web[spring-cloud-function-adapter-azure-web].
|
||||
With the Azure Web Adapter you can deploy any Spring Web application as an Azure, HttpTrigger, function.
|
||||
This adapter hides the Azure annotations complexity and uses the familiar https://docs.spring.io/spring-boot/docs/current/reference/html/web.html[Spring Web] programming model instead.
|
||||
For further information follow the <<azure.web.adapter,Azure Web Adapter>> section below.
|
||||
|
||||
== Azure Adapter
|
||||
|
||||
Provides `Spring` & `Spring Cloud Function` integration for Azure Functions.
|
||||
|
||||
=== Dependencies
|
||||
|
||||
In order to enable the Azure Function integration add the azure adapter dependency to your `pom.xml` or `build.gradle`
|
||||
files:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-azure</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure'
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: version `4.0.0+` is required. Having the adapter on the classpath activates the Azure Java Worker integration.
|
||||
|
||||
[[azure.development.guidelines]]
|
||||
=== Development Guidelines
|
||||
|
||||
Use the `@Component` (or `@Service`) annotation to turn any exiting Azure Function class (e.g. with `@FunctionName` handlers) into a Spring component.
|
||||
Then you can auto-wire the required dependencies (or the <<spring-cloud-function.adoc#function.catalog,Function Catalog>> for Spring Cloud Function composition) and use those inside the Azure function handlers.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Component // <1>
|
||||
public class MyAzureFunction {
|
||||
|
||||
// Plain Spring bean - not a Spring Cloud Functions!
|
||||
@Autowired private Function<String, String> uppercase; // <2>
|
||||
|
||||
// The FunctionCatalog leverages the Spring Cloud Function framework.
|
||||
@Autowired private FunctionCatalog functionCatalog; // <2>
|
||||
|
||||
@FunctionName("spring") // <3>
|
||||
public String plainBean( // <4>
|
||||
@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
|
||||
ExecutionContext context) {
|
||||
|
||||
return this.uppercase.apply(request.getBody().get());
|
||||
}
|
||||
|
||||
@FunctionName("scf") // <3>
|
||||
public String springCloudFunction( // <5>
|
||||
@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
|
||||
ExecutionContext context) {
|
||||
|
||||
// Use SCF composition. Composed functions are not just spring beans but SCF such.
|
||||
Function composed = this.functionCatalog.lookup("reverse|uppercase"); // <6>
|
||||
|
||||
return (String) composed.apply(request.getBody().get());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> Indicates that the `MyAzureFunction` class is a "component" to be considered by the Spring Framework as a candidate for auto-detection and classpath scanning.
|
||||
<2> Auto-wire the `uppercase` and `functionCatalog` beans defined in the `HttpTriggerDemoApplication` (below).
|
||||
<3> The https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-function-basics[@FunctionName] annotation identifies the designated Azure function handlers.
|
||||
When invoked by a trigger (such as `@HttpTrigger`), functions process that trigger, and any other inputs, to produce one or more outputs.
|
||||
<4> The `plainBean` method handler is mapped to an Azure function that uses of the auto-wired `uppercase` spring bean to compute the result.
|
||||
It demonstrates how to use "plain" Spring components in your Azure handlers.
|
||||
<5> The `springCloudFunction` method handler is mapped to another Azure function, that uses the auto-wired `FunctionCatalog` instance to compute the result.
|
||||
<6> Shows how to leverage the Spring Cloud Function <<spring-cloud-function.adoc#function.catalog,Function Catalog>> composition API.
|
||||
|
||||
TIP: Use the Java annotations included in the https://learn.microsoft.com/en-us/java/api/com.microsoft.azure.functions.annotation?view=azure-java-stable[com.microsoft.azure.functions.annotation.*] package to bind input and outputs to your methods.
|
||||
|
||||
The implementation of the business logic used inside the Azure handlers looks like a common Spring application:
|
||||
|
||||
[[HttpTriggerDemoApplication]]
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@SpringBootApplication // <1>
|
||||
public class HttpTriggerDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HttpTriggerDemoApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() { // <2>
|
||||
return payload -> payload.toUpperCase();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<String, String> reverse() { // <2>
|
||||
return payload -> new StringBuilder(payload).reverse().toString();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> The `@SpringBootApplication` annotated class is used as a `Main-Class` as explained in <<star-class-configuration, main class configuration>>.
|
||||
<2> Functions auto-wired and used in the Azure function handlers.
|
||||
|
||||
==== Function Catalog
|
||||
|
||||
The Spring Cloud Function supports a range of type signatures for user-defined functions, while providing a consistent execution model.
|
||||
For this it uses the <<spring-cloud-function.adoc#function.catalog,Function Catalog>> to transform all user defined functions into a canonical representation.
|
||||
|
||||
The Azure adapter can auto-wire any Spring component, such as the `uppercase` above.
|
||||
But those are treated as plain Java class instances, not as a canonical Spring Cloud Functions!
|
||||
|
||||
To leverage Spring Cloud Function and have access to the canonical function representations, you need to auto-wire the `FunctionCatalog` and use it in your handler, like the `functionCatalog` instance the `springCloudFunction()` handler above.
|
||||
|
||||
==== Accessing Azure ExecutionContext
|
||||
|
||||
Some time there is a need to access the target execution context provided by the Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`.
|
||||
For example one of such needs is logging, so it can appear in the Azure console.
|
||||
|
||||
For that purpose the `AzureFunctionUtil.enhanceInputIfNecessary` allow you to add an instance of the `ExecutionContext` as a Message header so you can retrieve it via `executionContext` key.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@FunctionName("myfunction")
|
||||
public String execute(
|
||||
@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
|
||||
ExecutionContext context) {
|
||||
|
||||
Message message =
|
||||
(Message) AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context); // <1>
|
||||
|
||||
return this.uppercase.apply(message);
|
||||
}
|
||||
----
|
||||
<1> Leverages the `AzureFunctionUtil` utility to inline the `context` as message header using the `AzureFunctionUtil.EXECUTION_CONTEXT` header key.
|
||||
|
||||
Now you can retrieve the ExecutionContext from message headers:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public Function<Message<String>, String> uppercase(JsonMapper mapper) {
|
||||
return message -> {
|
||||
String value = message.getPayload();
|
||||
ExecutionContext context =
|
||||
(ExecutionContext) message.getHeaders().get(AzureFunctionUtil.EXECUTION_CONTEXT); // <1>
|
||||
. . .
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Retrieve the ExecutionContext instance from the header.
|
||||
|
||||
|
||||
[[azure.configuration]]
|
||||
=== Configuration
|
||||
|
||||
To run your function applications on Microsoft Azure, you have to provide the necessary configurations, such as `function.json` and `host.json`, and adhere to the compulsory https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#folder-structure[packaging format].
|
||||
|
||||
Usually the Azure Maven (or Gradle) plugins are used to generate the necessary configurations from the annotated classes and to produce the required package format.
|
||||
|
||||
IMPORTANT: The Azure https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#folder-structure[packaging format] is not compatible with the default Spring Boot packaging (e.g. `uber jar`).
|
||||
The <<disable.spring.boot.plugin,Disable Spring Boot Plugin>> section below explains how to handle this.
|
||||
|
||||
==== Azure Maven/Gradle Plugins
|
||||
|
||||
Azure provides https://github.com/microsoft/azure-maven-plugins/tree/develop/azure-functions-maven-plugin[Maven] and https://github.com/microsoft/azure-gradle-plugins/tree/master/azure-functions-gradle-plugin[Gradle] plugins to process the annotated classes, generate the necessary configurations and produce the expected package layout.
|
||||
Plugins are used to set the platform, runtime and app-settings properties like this:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<plugin>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-functions-maven-plugin</artifactId>
|
||||
<version>1.22.0 or higher</version>
|
||||
|
||||
<configuration>
|
||||
<appName>YOUR-AZURE-FUNCTION-APP-NAME</appName>
|
||||
<resourceGroup>YOUR-AZURE-FUNCTION-RESOURCE-GROUP</resourceGroup>
|
||||
<region>YOUR-AZURE-FUNCTION-APP-REGION</region>
|
||||
<appServicePlanName>YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME</appServicePlanName>
|
||||
<pricingTier>YOUR-AZURE-FUNCTION-PRICING-TIER</pricingTier>
|
||||
|
||||
<hostJson>${project.basedir}/src/main/resources/host.json</hostJson>
|
||||
|
||||
<runtime>
|
||||
<os>linux</os>
|
||||
<javaVersion>11</javaVersion>
|
||||
</runtime>
|
||||
|
||||
<appSettings>
|
||||
<property>
|
||||
<name>FUNCTIONS_EXTENSION_VERSION</name>
|
||||
<value>~4</value>
|
||||
</property>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>package-functions</id>
|
||||
<goals>
|
||||
<goal>package</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
plugins {
|
||||
id "com.microsoft.azure.azurefunctions" version "1.11.0"
|
||||
// ...
|
||||
}
|
||||
|
||||
apply plugin: "com.microsoft.azure.azurefunctions"
|
||||
|
||||
azurefunctions {
|
||||
appName = 'YOUR-AZURE-FUNCTION-APP-NAME'
|
||||
resourceGroup = 'YOUR-AZURE-FUNCTION-RESOURCE-GROUP'
|
||||
region = 'YOUR-AZURE-FUNCTION-APP-REGION'
|
||||
appServicePlanName = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME'
|
||||
pricingTier = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME'
|
||||
|
||||
runtime {
|
||||
os = 'linux'
|
||||
javaVersion = '11'
|
||||
}
|
||||
|
||||
auth {
|
||||
type = 'azure_cli'
|
||||
}
|
||||
|
||||
appSettings {
|
||||
FUNCTIONS_EXTENSION_VERSION = '~4'
|
||||
}
|
||||
// Uncomment to enable local debug
|
||||
// localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
More information about the runtime configurations: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-versions[Java Versions], https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#specify-the-deployment-os[Deployment OS].
|
||||
|
||||
[[disable.spring.boot.plugin]]
|
||||
==== Disable Spring Boot Plugin
|
||||
|
||||
Expectedly, the Azure Functions run inside the Azure execution runtime, not inside the SpringBoot runtime!
|
||||
Furthermore, Azure expects a specific packaging format, generated by the Azure Maven/Gradle plugins, that is not compatible with the default Spring Boot packaging.
|
||||
|
||||
You have to either disable the SpringBoot Maven/Gradle plugin or use the https://github.com/dsyer/spring-boot-thin-launcher[Spring Boot Thin Launcher] as shown in this Maven snippet:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot.experimental</groupId>
|
||||
<artifactId>spring-boot-thin-layout</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
[[star-class-configuration]]
|
||||
==== Main-Class Configuration
|
||||
|
||||
Specify the `Main-Class`/`Start-Class` to point to your Spring application entry point, such as the <<HttpTriggerDemoApplication,HttpTriggerDemoApplication>> class in the example above.
|
||||
|
||||
You can use the Maven `start-class` property or set the `Main-Class` attribute of your `MANIFEST/META-INFO`:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<properties>
|
||||
<start-class>YOUR APP MAIN CLASS</start-class>
|
||||
...
|
||||
</properties>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
"Main-Class": "YOUR-APP-MAIN-CLASS"
|
||||
)
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
TIP: Alternatively you can use the `MAIN_CLASS` environment variable to set the class name explicitly.
|
||||
For local runs, add the `MAIN_CLASS` variable to your `local.settings.json` file and for Azure portal deployment set the variable in the https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#get-started-in-the-azure-portal[App Settings].
|
||||
|
||||
|
||||
IMPORTANT: If the `MAIN_CLASS` variable is not set, the Azure adapter lookups the `MANIFEST/META-INFO` attributes from the jars found on the classpath and selects the first `Main-Class:` annotated with either a `@SpringBootApplication` or `@SpringBootConfiguration` annotation.
|
||||
|
||||
==== Metadata Configuration
|
||||
|
||||
You can use a shared https://learn.microsoft.com/en-us/azure/azure-functions/functions-host-json[host.json] file to configure the function app.
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"version": "2.0",
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The host.json metadata file contains configuration options that affect all functions in a function app instance.
|
||||
|
||||
TIP: If the file is not in the project top folder you need to configure your plugins accordingly (like `hostJson` maven attribute).
|
||||
|
||||
=== Samples
|
||||
|
||||
Here is a list of various Spring Cloud Function Azure Adapter samples you can explore:
|
||||
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger[Http Trigger (Maven)]
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle[Http Trigger (Gradle)]
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-blob-trigger[Blob Trigger (Maven)]
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-timer-trigger[Timer Trigger (Maven)]
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-kafka-trigger[ Kafka Trigger & Output Binding (Maven)].
|
||||
|
||||
[[azure.web.adapter]]
|
||||
== Azure Web Adapter
|
||||
|
||||
For, pure, Web-based function applications, you can replace the generic `adapter-azure` with the specialized https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web[spring-cloud-function-adapter-azure-web].
|
||||
The Azure Web Adapter can deploy any Spring Web application as a native Azure function, using the HttpTrigger internally.
|
||||
It hides the Azure annotations complexity and relies on the familiar https://docs.spring.io/spring-boot/docs/current/reference/html/web.html[Spring Web] programming model instead.
|
||||
|
||||
To enable the Azure Web Adapter, add the adapter dependency to your `pom.xml` or `build.gradle` files:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-azure-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure-web'
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The same <<azure.configuration, Configuration>> and <<azure.usage,Usage>> instructions apply to the `Azure Web Adapter` as well.
|
||||
|
||||
|
||||
=== Samples
|
||||
|
||||
For further information, explore the following, Azure Web Adapter, sample:
|
||||
|
||||
- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-web[ Azure Web Adapter (Maven)].
|
||||
|
||||
[[azure.usage]]
|
||||
== Usage
|
||||
|
||||
Common instructions for building and deploying both, `Azure Adapter` and `Azure Web Adapter` type of applications.
|
||||
|
||||
=== Build
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
./mvnw -U clean package
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
./gradlew azureFunctionsPackage
|
||||
----
|
||||
====
|
||||
|
||||
=== Running locally
|
||||
|
||||
To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here]).
|
||||
For some configuration you would need the https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator[Azurite emulator] as well.
|
||||
|
||||
Then run the sample:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
./mvnw azure-functions:run
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
./gradlew azureFunctionsRun
|
||||
----
|
||||
====
|
||||
|
||||
=== Running on Azure
|
||||
|
||||
Make sure you are logged in your Azure account.
|
||||
|
||||
----
|
||||
az login
|
||||
----
|
||||
|
||||
and deploy
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
./mvnw azure-functions:deploy
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
./gradlew azureFunctionsDeploy
|
||||
----
|
||||
====
|
||||
|
||||
=== Debug locally
|
||||
|
||||
Run the function in debug mode.
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
./mvnw azure-functions:run -DenableDebug
|
||||
----
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
|
||||
----
|
||||
// If you want to debug your functions, please add the following line
|
||||
// to the azurefunctions section of your build.gradle.
|
||||
azurefunctions {
|
||||
...
|
||||
localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Alternatively and the `JAVA_OPTS` value to your `local.settings.json` like this:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
...
|
||||
"FUNCTIONS_WORKER_RUNTIME": "java",
|
||||
"JAVA_OPTS": "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Here is snippet for a `VSCode` remote debugging configuration:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Attach to Remote Program",
|
||||
"request": "attach",
|
||||
"hostName": "localhost",
|
||||
"port": "5005"
|
||||
},
|
||||
]
|
||||
}
|
||||
----
|
||||
|
||||
== FunctionInvoker (deprecated)
|
||||
|
||||
WARNING: The legacy `FunctionInvoker` programming model is deprecated and will not be supported going forward.
|
||||
|
||||
For additional documentation and samples about the Function Integration approach follow the https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure/[azure-sample] README and code.
|
||||
|
||||
== Relevant Links
|
||||
|
||||
- https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/getting-started-with-spring-cloud-function-in-azure[Spring Cloud Function in Azure]
|
||||
- https://spring.io/blog/2023/02/24/spring-cloud-function-for-azure-function[Spring Cloud Function for Azure Function (blog)]
|
||||
- <<spring-cloud-function.adoc#,Spring Cloud Function - Reference Guide>>
|
||||
- https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption[Azure Functions Java developer guide]
|
||||
- https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference?tabs=blob[Azure Functions developer guide]
|
||||
|
||||
:sectnums!:
|
||||
@@ -1,3 +0,0 @@
|
||||
*{project-version}*
|
||||
|
||||
include::adapters/azure-intro.adoc[]
|
||||
@@ -1,295 +0,0 @@
|
||||
:branch: master
|
||||
|
||||
=== Google Cloud Functions
|
||||
|
||||
The Google Cloud Functions adapter enables Spring Cloud Function apps to run on the https://cloud.google.com/functions[Google Cloud Functions] serverless platform.
|
||||
You can either run the function locally using the open source https://github.com/GoogleCloudPlatform/functions-framework-java[Google Functions Framework for Java] or on GCP.
|
||||
|
||||
==== Project Dependencies
|
||||
|
||||
Start by adding the `spring-cloud-function-adapter-gcp` dependency to your project.
|
||||
|
||||
[source, xml]
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-gcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
...
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
In addition, add the `spring-boot-maven-plugin` which will build the JAR of the function to deploy.
|
||||
|
||||
NOTE: Notice that we also reference `spring-cloud-function-adapter-gcp` as a dependency of the `spring-boot-maven-plugin`. This is necessary because it modifies the plugin to package your function in the correct JAR format for deployment on Google Cloud Functions.
|
||||
|
||||
[source, xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<outputDirectory>target/deploy</outputDirectory>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-gcp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
Finally, add the Maven plugin provided as part of the Google Functions Framework for Java.
|
||||
This allows you to test your functions locally via `mvn function:run`.
|
||||
|
||||
NOTE: The function target should always be set to `org.springframework.cloud.function.adapter.gcp.GcfJarLauncher`; this is an adapter class which acts as the entry point to your Spring Cloud Function from the Google Cloud Functions platform.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.functions</groupId>
|
||||
<artifactId>function-maven-plugin</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<configuration>
|
||||
<functionTarget>org.springframework.cloud.function.adapter.gcp.GcfJarLauncher</functionTarget>
|
||||
<port>8080</port>
|
||||
</configuration>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
A full example of a working `pom.xml` can be found in the https://github.com/spring-cloud/spring-cloud-function/blob/master/spring-cloud-function-samples/function-sample-gcp-http/pom.xml[Spring Cloud Functions GCP sample].
|
||||
|
||||
==== HTTP Functions
|
||||
|
||||
Google Cloud Functions supports deploying https://cloud.google.com/functions/docs/writing/http[HTTP Functions], which are functions that are invoked by HTTP request. The sections below describe instructions for deploying a Spring Cloud Function as an HTTP Function.
|
||||
|
||||
===== Getting Started
|
||||
|
||||
Let’s start with a simple Spring Cloud Function example:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class CloudFunctionMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CloudFunctionMain.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() {
|
||||
return value -> value.toUpperCase();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Specify your configuration main class in `resources/META-INF/MANIFEST.MF`.
|
||||
|
||||
[source]
|
||||
----
|
||||
Main-Class: com.example.CloudFunctionMain
|
||||
----
|
||||
|
||||
Then run the function locally.
|
||||
This is provided by the Google Cloud Functions `function-maven-plugin` described in the project dependencies section.
|
||||
|
||||
----
|
||||
mvn function:run
|
||||
----
|
||||
|
||||
Invoke the HTTP function:
|
||||
|
||||
----
|
||||
curl http://localhost:8080/ -d "hello"
|
||||
----
|
||||
|
||||
===== Deploy to GCP
|
||||
|
||||
Start by packaging your application.
|
||||
|
||||
----
|
||||
mvn package
|
||||
----
|
||||
|
||||
If you added the custom `spring-boot-maven-plugin` plugin defined above, you should see the resulting JAR in `target/deploy` directory.
|
||||
This JAR is correctly formatted for deployment to Google Cloud Functions.
|
||||
|
||||
Next, make sure that you have the https://cloud.google.com/sdk/install[Cloud SDK CLI] installed.
|
||||
|
||||
From the project base directory run the following command to deploy.
|
||||
|
||||
----
|
||||
gcloud functions deploy function-sample-gcp-http \
|
||||
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
|
||||
--runtime java11 \
|
||||
--trigger-http \
|
||||
--source target/deploy \
|
||||
--memory 512MB
|
||||
----
|
||||
|
||||
Invoke the HTTP function:
|
||||
|
||||
----
|
||||
curl https://REGION-PROJECT_ID.cloudfunctions.net/function-sample-gcp-http -d "hello"
|
||||
----
|
||||
|
||||
|
||||
Setting custom HTTP statusCode:
|
||||
|
||||
----
|
||||
Functions can specify a custom HTTP response code by setting the `FunctionInvoker.HTTP_STATUS_CODE` header.
|
||||
----
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public Function<String, Message<String>> function() {
|
||||
|
||||
String payload = "hello";
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload(payload).setHeader(FunctionInvoker.HTTP_STATUS_CODE, 404).build();
|
||||
|
||||
return input -> message;
|
||||
};
|
||||
----
|
||||
|
||||
|
||||
|
||||
==== Background Functions
|
||||
|
||||
Google Cloud Functions also supports deploying https://cloud.google.com/functions/docs/writing/background[Background Functions] which are invoked indirectly in response to an event, such as a message on a https://cloud.google.com/pubsub[Cloud Pub/Sub] topic, a change in a https://cloud.google.com/storage[Cloud Storage] bucket, or a https://firebase.google.com/[Firebase] event.
|
||||
|
||||
The `spring-cloud-function-adapter-gcp` allows for functions to be deployed as background functions as well.
|
||||
|
||||
The sections below describe the process for writing a Cloud Pub/Sub topic background function.
|
||||
However, there are a number of different event types that can trigger a background function to execute which are not discussed here; these are described in the https://cloud.google.com/functions/docs/calling[Background Function triggers documentation].
|
||||
|
||||
===== Getting Started
|
||||
|
||||
Let’s start with a simple Spring Cloud Function which will run as a GCF background function:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class BackgroundFunctionMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BackgroundFunctionMain.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Consumer<PubSubMessage> pubSubFunction() {
|
||||
return message -> System.out.println("The Pub/Sub message data: " + message.getData());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In addition, create `PubSubMessage` class in the project with the below definition.
|
||||
This class represents the https://cloud.google.com/functions/docs/calling/pubsub#event_structure[Pub/Sub event structure] which gets passed to your function on a Pub/Sub topic event.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class PubSubMessage {
|
||||
|
||||
private String data;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
|
||||
private String messageId;
|
||||
|
||||
private String publishTime;
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public String getPublishTime() {
|
||||
return publishTime;
|
||||
}
|
||||
|
||||
public void setPublishTime(String publishTime) {
|
||||
this.publishTime = publishTime;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Specify your configuration main class in `resources/META-INF/MANIFEST.MF`.
|
||||
|
||||
[source]
|
||||
----
|
||||
Main-Class: com.example.BackgroundFunctionMain
|
||||
----
|
||||
|
||||
Then run the function locally.
|
||||
This is provided by the Google Cloud Functions `function-maven-plugin` described in the project dependencies section.
|
||||
|
||||
----
|
||||
mvn function:run
|
||||
----
|
||||
|
||||
Invoke the HTTP function:
|
||||
|
||||
----
|
||||
curl localhost:8080 -H "Content-Type: application/json" -d '{"data":"hello"}'
|
||||
----
|
||||
|
||||
Verify that the function was invoked by viewing the logs.
|
||||
|
||||
===== Deploy to GCP
|
||||
|
||||
In order to deploy your background function to GCP, first package your application.
|
||||
|
||||
----
|
||||
mvn package
|
||||
----
|
||||
|
||||
If you added the custom `spring-boot-maven-plugin` plugin defined above, you should see the resulting JAR in `target/deploy` directory.
|
||||
This JAR is correctly formatted for deployment to Google Cloud Functions.
|
||||
|
||||
Next, make sure that you have the https://cloud.google.com/sdk/install[Cloud SDK CLI] installed.
|
||||
|
||||
From the project base directory run the following command to deploy.
|
||||
|
||||
----
|
||||
gcloud functions deploy function-sample-gcp-background \
|
||||
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
|
||||
--runtime java11 \
|
||||
--trigger-topic my-functions-topic \
|
||||
--source target/deploy \
|
||||
--memory 512MB
|
||||
----
|
||||
|
||||
Google Cloud Function will now invoke the function every time a message is published to the topic specified by `--trigger-topic`.
|
||||
|
||||
For a walkthrough on testing and verifying your background function, see the instructions for running the https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-gcp-background/[GCF Background Function sample].
|
||||
|
||||
==== Sample Functions
|
||||
|
||||
The project provides the following sample functions as reference:
|
||||
|
||||
* The https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-gcp-http/[function-sample-gcp-http] is an HTTP Function which you can test locally and try deploying.
|
||||
* The https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-gcp-background/[function-sample-gcp-background] shows an example of a background function that is triggered by a message being published to a specified Pub/Sub topic.
|
||||
@@ -1,3 +0,0 @@
|
||||
*{project-version}*
|
||||
|
||||
include::adapters/gcp-intro.adoc[]
|
||||
@@ -1,316 +0,0 @@
|
||||
Spring Cloud Function supports a "functional" style of bean declarations for small apps where you need fast startup. The functional style of bean declaration was a feature of Spring Framework 5.0 with significant enhancements in 5.1.
|
||||
|
||||
== Comparing Functional with Traditional Bean Definitions
|
||||
|
||||
Here's a vanilla Spring Cloud Function application from with the
|
||||
familiar `@Configuration` and `@Bean` declaration style:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() {
|
||||
return value -> value.toUpperCase();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now for the functional beans: the user application code can be recast into "functional"
|
||||
form, like this:
|
||||
|
||||
```java
|
||||
@SpringBootConfiguration
|
||||
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
public static void main(String[] args) {
|
||||
FunctionalSpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
public Function<String, String> uppercase() {
|
||||
return value -> value.toUpperCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
context.registerBean("demo", FunctionRegistration.class,
|
||||
() -> new FunctionRegistration<>(uppercase())
|
||||
.type(FunctionTypeUtils.functionType(String.class, String.class)));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The main differences are:
|
||||
|
||||
* The main class is an `ApplicationContextInitializer`.
|
||||
|
||||
* The `@Bean` methods have been converted to calls to `context.registerBean()`
|
||||
|
||||
* The `@SpringBootApplication` has been replaced with
|
||||
`@SpringBootConfiguration` to signify that we are not enabling Spring
|
||||
Boot autoconfiguration, and yet still marking the class as an "entry
|
||||
point".
|
||||
|
||||
* The `SpringApplication` from Spring Boot has been replaced with a
|
||||
`FunctionalSpringApplication` from Spring Cloud Function (it's a
|
||||
subclass).
|
||||
|
||||
The business logic beans that you register in a Spring Cloud Function app are of type `FunctionRegistration`.
|
||||
This is a wrapper that contains both the function and information about the input and output types. In the `@Bean`
|
||||
form of the application that information can be derived reflectively, but in a functional bean registration some of
|
||||
it is lost unless we use a `FunctionRegistration`.
|
||||
|
||||
An alternative to using an `ApplicationContextInitializer` and `FunctionRegistration` is to make the application
|
||||
itself implement `Function` (or `Consumer` or `Supplier`). Example (equivalent to the above):
|
||||
|
||||
```java
|
||||
@SpringBootConfiguration
|
||||
public class DemoApplication implements Function<String, String> {
|
||||
|
||||
public static void main(String[] args) {
|
||||
FunctionalSpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String value) {
|
||||
return value.toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
It would also work if you add a separate, standalone class of type `Function` and register it with
|
||||
the `SpringApplication` using an alternative form of the `run()` method. The main thing is that the generic
|
||||
type information is available at runtime through the class declaration.
|
||||
|
||||
Suppose you have
|
||||
[source, java]
|
||||
----
|
||||
@Component
|
||||
public class CustomFunction implements Function<Flux<Foo>, Flux<Bar>> {
|
||||
@Override
|
||||
public Flux<Bar> apply(Flux<Foo> flux) {
|
||||
return flux.map(foo -> new Bar("This is a Bar object from Foo value: " + foo.getValue()));
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
You register it as such:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
context.registerBean("function", FunctionRegistration.class,
|
||||
() -> new FunctionRegistration<>(new CustomFunction()).type(CustomFunction.class));
|
||||
}
|
||||
----
|
||||
|
||||
== Limitations of Functional Bean Declaration
|
||||
|
||||
Most Spring Cloud Function apps have a relatively small scope compared to the whole of Spring Boot,
|
||||
so we are able to adapt it to these functional bean definitions easily. If you step outside that limited scope,
|
||||
you can extend your Spring Cloud Function app by switching back to `@Bean` style configuration, or by using a hybrid
|
||||
approach. If you want to take advantage of Spring Boot autoconfiguration for integrations with external datastores,
|
||||
for example, you will need to use `@EnableAutoConfiguration`. Your functions can still be defined using the functional
|
||||
declarations if you want (i.e. the "hybrid" style), but in that case you will need to explicitly switch off the "full
|
||||
functional mode" using `spring.functional.enabled=false` so that Spring Boot can take back control.
|
||||
|
||||
[[function_visualization]]
|
||||
= Function visualization and control
|
||||
|
||||
Spring Cloud Function supports visualization of functions available in `FunctionCatalog` through Actuator endpoints as well as programmatic way.
|
||||
|
||||
==== Programmatic way
|
||||
|
||||
To see function available within your application context programmatically all you need is access to `FunctionCatalog`. There you can
|
||||
finds methods to get the size of the catalog, lookup functions as well as list the names of all the available functions.
|
||||
|
||||
For example,
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
|
||||
int size = functionCatalog.size(); // will tell you how many functions available in catalog
|
||||
Set<String> names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog
|
||||
. . .
|
||||
----
|
||||
|
||||
==== Actuator
|
||||
Since actuator and web are optional, you must first add one of the web dependencies as well as add the actuator dependency manually.
|
||||
The following example shows how to add the dependency for the Web framework:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
The following example shows how to add the dependency for the WebFlux framework:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
You can add the Actuator dependency as follows:
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
You must also enable the `functions` actuator endpoints by setting the following property: `--management.endpoints.web.exposure.include=functions`.
|
||||
|
||||
Access the following URL to see the functions in FunctionCatalog:
|
||||
`http://<host>:<port>/actuator/functions`
|
||||
|
||||
For example,
|
||||
[source,text]
|
||||
----
|
||||
curl http://localhost:8080/actuator/functions
|
||||
----
|
||||
|
||||
Your output should look something like this:
|
||||
[source,text]
|
||||
----
|
||||
{"charCounter":
|
||||
{"type":"FUNCTION","input-type":"string","output-type":"integer"},
|
||||
"logger":
|
||||
{"type":"CONSUMER","input-type":"string"},
|
||||
"functionRouter":
|
||||
{"type":"FUNCTION","input-type":"object","output-type":"object"},
|
||||
"words":
|
||||
{"type":"SUPPLIER","output-type":"string"}. . .
|
||||
----
|
||||
|
||||
= Testing Functional Applications
|
||||
|
||||
Spring Cloud Function also has some utilities for integration testing that will be very familiar to Spring Boot users.
|
||||
|
||||
Suppose this is your application:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class SampleFunctionApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleFunctionApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<String, String> uppercase() {
|
||||
return v -> v.toUpperCase();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Here is an integration test for the HTTP server wrapping this application:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootTest(classes = SampleFunctionApplication.class,
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class WebFunctionTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate rest;
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
ResponseEntity<String> result = this.rest.exchange(
|
||||
RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
|
||||
System.out.println(result.getBody());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
or when function bean definition style is used:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@FunctionalSpringBootTest
|
||||
public class WebFunctionTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate rest;
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
ResponseEntity<String> result = this.rest.exchange(
|
||||
RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
|
||||
System.out.println(result.getBody());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This test is almost identical to the one you would write for the `@Bean` version of the same app - the only difference
|
||||
is the `@FunctionalSpringBootTest` annotation, instead of the regular `@SpringBootTest`. All the other pieces,
|
||||
like the `@Autowired` `TestRestTemplate`, are standard Spring Boot features.
|
||||
|
||||
And to help with correct dependencies here is the excerpt from POM
|
||||
|
||||
[source, xml, subs=attributes+]
|
||||
----
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>{spring-boot-version}</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
. . . .
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-web</artifactId>
|
||||
<version>{project-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
Or you could write a test for a non-HTTP app using just the `FunctionCatalog`. For example:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@FunctionalSpringBootTest
|
||||
public class FunctionalTests {
|
||||
|
||||
@Autowired
|
||||
private FunctionCatalog catalog;
|
||||
|
||||
@Test
|
||||
public void words() {
|
||||
Function<String, String> function = catalog.lookup(Function.class,
|
||||
"uppercase");
|
||||
assertThat(function.apply("hello")).isEqualTo("HELLO");
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
@@ -1,33 +0,0 @@
|
||||
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/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 `^Q^J` in a terminal to insert a new line in a literal
|
||||
string like that.)
|
||||
@@ -1,23 +0,0 @@
|
||||
= Spring Cloud Function Reference Documentation
|
||||
Mark Fisher, Dave Syer, Oleg Zhurakousky, Anshul Mehra, Dan Dobrin, Chris Bono, Artem Bilan
|
||||
|
||||
*{project-version}*
|
||||
|
||||
:docinfo: shared
|
||||
|
||||
The reference documentation consists of the following sections:
|
||||
|
||||
[horizontal]
|
||||
<<spring-cloud-function.adoc#,Reference Guide>> :: Spring Cloud Function Reference
|
||||
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-cloudevent[Cloud Events] :: Cloud Events
|
||||
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-rsocket[RSocket] :: RSocket
|
||||
<<./spring-integration.adoc#spring-integration,Spring Integration>> :: Spring Integration Framework Interaction
|
||||
<<aws.adoc#,AWS Adapter>> :: AWS Adapter Reference
|
||||
<<azure.adoc#, Azure Adapter>> :: Azure Adapter Reference
|
||||
<<gcp.adoc#, GCP Adapter>> :: GCP Adapter Reference
|
||||
|
||||
|
||||
Relevant Links:
|
||||
|
||||
[horizontal]
|
||||
https://projectreactor.io/[Reactor] :: Project Reactor
|
||||
@@ -1,46 +0,0 @@
|
||||
Spring Cloud Function is a project with the following high-level goals:
|
||||
|
||||
* Promote the implementation of business logic via functions.
|
||||
* Decouple the development lifecycle of business logic from any specific runtime target so that the same code can run as a web endpoint, a stream processor, or a task.
|
||||
* Support a uniform programming model across serverless providers, as well as the ability to run standalone (locally or in a PaaS).
|
||||
* Enable Spring Boot features (auto-configuration, dependency injection, metrics) on serverless providers.
|
||||
|
||||
It abstracts 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.
|
||||
|
||||
## Features
|
||||
|
||||
Spring Cloud Function features:
|
||||
|
||||
* _Choice of programming styles - reactive, imperative or hybrid._
|
||||
* _Function composition and adaptation (e.g., composing imperative functions with reactive)._
|
||||
* _Support for reactive function with multiple inputs and outputs allowing merging, joining and other complex streaming operation to be handled by functions._
|
||||
* _Transparent type conversion of inputs and outputs._
|
||||
* _Packaging functions for deployments, specific to the target platform (e.g., Project Riff, AWS Lambda and more)_
|
||||
* _Adapters to expose function to the outside world as HTTP endpoints etc._
|
||||
* _Deploying a JAR file containing such an application context with an isolated classloader, so that you can pack them together in a single JVM._
|
||||
* _Adapters for https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-aws[AWS Lambda], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[Microsoft Azure], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp[Google Cloud Functions], and possibly other "serverless" service providers._
|
||||
|
||||
Here's a complete, executable, testable Spring Boot application (implementing a simple string manipulation):
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<Flux<String>, Flux<String>> uppercase() {
|
||||
return flux -> flux.map(value -> value.toUpperCase());
|
||||
}
|
||||
}
|
||||
```
|
||||
### Sample Projects
|
||||
|
||||
* https://github.com/spring-cloud/spring-cloud-function/blob/master/spring-cloud-function-samples/function-sample[Vanilla]
|
||||
* https://github.com/spring-cloud/spring-cloud-function/blob/master/spring-cloud-function-samples/function-sample-pof[Plain Old Function]
|
||||
* https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws[AWS Lambda]
|
||||
* https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-azure[Microsoft Azure]
|
||||
* https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-gcp-http[Google Cloud Functions]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
[[spring-integration]]
|
||||
== Spring Integration Interaction
|
||||
|
||||
https://spring.io/projects/spring-integration[Spring Integration Framework] extends the Spring programming model to support the well-known Enterprise Integration Patterns.
|
||||
It enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters.
|
||||
It also provides a high-level DSL to compose various operations (endpoints) into a logical integration flow.
|
||||
With a lambda style of this DSL configuration, Spring Integration already has a good level of `java.util.function` interfaces adoption.
|
||||
The `@MessagingGateway` proxy interface can also be as a `Function` or `Consumer`, which according to the Spring Cloud Function environment can be registered into a function catalog.
|
||||
See more information in Spring Integration https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#functions-support[ReferenceManual] about its support for functions.
|
||||
|
||||
On the other hand, starting with version `4.0.3`, Spring Cloud Function introduces a `spring-cloud-function-integration` module which provides deeper, more cloud-specific and auto-configuration based API for interaction with a `FunctionCatalog` from Spring Integration DSL perspective.
|
||||
The `FunctionFlowBuilder` is auto-configured and autowired with a `FunctionCatalog` and represents an entry point for function-specific DSL for target `IntegrationFlow` instance.
|
||||
In addition to standard `IntegrationFlow.from()` factories (for convenience), the `FunctionFlowBuilder` exposes a `fromSupplier(String supplierDefinition)` factory to lookup the target `Supplier` in the provided `FunctionCatalog`.
|
||||
Then this `FunctionFlowBuilder` leads to the `FunctionFlowDefinition`.
|
||||
This `FunctionFlowDefinition` is an implementation of the `IntegrationFlowExtension` and exposes `apply(String functionDefinition)` and `accept(String consumerDefinition)` operators to lookup `Function` or `Consumer` from the `FunctionCatalog`, respectively.
|
||||
See their Javadocs for more information.
|
||||
|
||||
The following example demonstrates the `FunctionFlowBuilder` in action alongside with the power of the rest of `IntegrationFlow` API:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class IntegrationConfiguration {
|
||||
|
||||
@Bean
|
||||
Supplier<byte[]> simpleByteArraySupplier() {
|
||||
return "simple test data"::getBytes;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Function<String, String> upperCaseFunction() {
|
||||
return String::toUpperCase;
|
||||
}
|
||||
|
||||
@Bean
|
||||
BlockingQueue<String> results() {
|
||||
return new LinkedBlockingQueue<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<String> simpleStringConsumer(BlockingQueue<String> results) {
|
||||
return results::add;
|
||||
}
|
||||
|
||||
@Bean
|
||||
QueueChannel wireTapChannel() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
|
||||
return functionFlowBuilder
|
||||
.fromSupplier("simpleByteArraySupplier")
|
||||
.wireTap("wireTapChannel")
|
||||
.apply("upperCaseFunction")
|
||||
.log(LoggingHandler.Level.WARN)
|
||||
.accept("simpleStringConsumer");
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Since the `FunctionCatalog.lookup()` functionality is not limited just to simple function names, a function composition feature can also be used in the mentioned `apply()` and `accept()` operators:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
IntegrationFlow functionCompositionFlow(FunctionFlowBuilder functionFlowBuilder) {
|
||||
return functionFlowBuilder
|
||||
.from("functionCompositionInput")
|
||||
.accept("upperCaseFunction|simpleStringConsumer");
|
||||
}
|
||||
----
|
||||
|
||||
This API becomes more relevant, when we add into our Spring Cloud applications auto-configuration dependencies for predefined functions.
|
||||
For example https://spring.io/projects/spring-cloud-stream-applications[Stream Applications] project, in addition to application images, provides artifacts with functions for various integration use-case, e.g. `debezium-supplier`, `elasticsearch-consumer`, `aggregator-function` etc.
|
||||
|
||||
The following configuration is based on the `http-supplier`, `spel-function` and `file-consumer`, respectively:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
|
||||
return functionFlowBuilder
|
||||
.fromSupplier("httpSupplier", e -> e.poller(Pollers.trigger(new OnlyOnceTrigger())))
|
||||
.<Flux<?>>handle((fluxPayload, headers) -> fluxPayload, e -> e.async(true))
|
||||
.channel(c -> c.flux())
|
||||
.apply("spelFunction")
|
||||
.<String, String>transform(String::toUpperCase)
|
||||
.accept("fileConsumer");
|
||||
}
|
||||
----
|
||||
|
||||
What we would need else is just to add their configuration into an `application.properties` (if necessary):
|
||||
|
||||
[source,properties]
|
||||
----
|
||||
http.path-pattern=/testPath
|
||||
spel.function.expression=new String(payload)
|
||||
file.consumer.name=test-data.txt
|
||||
----
|
||||
Reference in New Issue
Block a user