Streamline and refactor the azure and the azure-web adapters.

- Add SCF/Azure Gradle sample and docs.
- Move the function-azure-di-samples into standalone projects.
 - Apply the name convetion and project structure for the SCF adaptes.
   E.g.  function-sample-azure-XXX projects under the spring-cloud-function-samples root.
 - Remove the redudant samples.
 - Improve the samples docs and the Adapter generic docs.
- Streamline docs.
- Add azure web adapter sample and README.
- Add Spring Azure Functions banner for azure and azure web adapters.
- azure-web adapter fixes:
  - Fix issues in serverles-web ProxyHttpServletResponse implementation.
  - Remove the custom FunctionClassUtils utils in favor of scf-context/util/FunctionClassUtils.
- Remove redundant files.
- Add FunctionInvoker deprecation annotations.
- Extend the time trigger sample with Retry policies example.
This commit is contained in:
Christian Tzolov
2023-07-14 18:46:42 +02:00
parent 64f57bcef8
commit 6299a5366b
88 changed files with 1834 additions and 1312 deletions

View File

@@ -1,18 +1,31 @@
:branch: master
=== Microsoft Azure
=== Microsoft Azure Functions
The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure framework into the user functions, using Spring Boot configuration where necessary.
Azure Functions has quite a unique and invasive programming model, involving annotations in user code that are specific to the Azure platform.
However, it is important to understand that because of the style of integration provided by Spring Cloud Function, this annotation-based programming model is simply a type-safe way to configure your simple java function (function that has no awareness of Azure) to be recognized as Azure function.
The https://azure.microsoft.com[Azure] adapter, that allows to deploy and run Spring Cloud Functions as native Azure Java Functions.
All you need to annotate the your class with `@Component` or `@Service` annotations, auto-wire the required Spring beans (or the `FunctionCatalog` when using Spring Cloud Function), define and configure your Azure function handler. This Azure handler method provides input and output types as annotated method parameters (enabling Azure to inspect the class and create JSON bindings).
The Azure impose an annotation-based https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java[programming model] for defining the function's handler methods and their input and output types.
The Azure's maven or gradle plugins are used to inspects the annotated class definitions to generate the necessary Azure Function binding files (such as function.json).
The annotations are just a type-safe way to configure your simple java function (function that has no awareness of Azure) 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 and provides fully fledged Spring and Spring Cloud Function programming model support.
With the adapter you can build your, usual, Spring Cloud Function application and then auto-wire and use the required services from within your Azure handler methods.
TIP: For pure web based function applications, the 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] adapter allows replacing the Azure programming model completely with the familiar Spring Web programming model. You just build your Spring Web app, add the azure-web adapter dependency and the necessarily azure layout packaging. You can find more about the azure-web adapter [here]
==== Using the Azure Adapter
All you need to annotate the your class with `@Component` or `@Service` annotations, auto-wire the required Spring beans (or the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_function_catalog_and_flexible_function_signatures[FunctionCatalog] when using Spring Cloud Function), define and configure your Azure function handler.
[source,java]
----
@Component
@SpringBootApplication
public class MyAzureFunction {
public static void main(String[] args) {
SpringApplication.run(MyAzureFunction.class, args);
}
/**
* Plain Spring bean (not Spring Cloud Functions!)
*/
@@ -25,8 +38,8 @@ public class MyAzureFunction {
@Autowired
private FunctionCatalog functionCatalog;
@FunctionName("bean")
public String plainBean(
@FunctionName("spring")
public String plainBean( // <1>
@HttpTrigger(name = "req", methods = { HttpMethod.GET,
HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
@@ -35,7 +48,7 @@ public class MyAzureFunction {
}
@FunctionName("scf")
public String springCloudFunction(
public String springCloudFunction( // <2>
@HttpTrigger(name = "req", methods = { HttpMethod.GET,
HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
@@ -47,10 +60,15 @@ public class MyAzureFunction {
}
}
----
Azure's programming-model uses the https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-function-basics[@FunctionName] method annotation to identify the designated function handlers.
When invoked by a trigger (such as `@HttpTrigger`), functions process that trigger, and any other inputs, to produce one or more outputs.
The `plainBean` method will be mapped to the `bean` Azure function and when executed this method uses of the plain `uppercase` bean to compute the result.
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 `springCloudFunction` method, mapped to the `scf` Azure function shows how to use the Spring Cloud Function composition.
<1> The `plainBean` method handler is mapped to an Azure function, called `spring`, and when executed is uses of the auto-wired `uppercase` spring bean to compute the result.
This demonstrates how to use "plain" Spring components in your Azure handlers.
<2> The `springCloudFunction` method handler is mapped to the `scf` Azure function and shows how to use Spring Cloud Function composition in your handles.
The actual Spring defined functions you're delegating to looks like this:
@@ -67,22 +85,35 @@ public Function<String, String> reverse() {
}
----
In order to enable the Azure Function integration add the azure adapter dependency to your `pom.xml`
file:
In order to enable the Azure Function integration add the azure adapter dependency to your `pom.xml` or `build.gradle`
files:
[source,xml]
====
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
<version>4.0.3</version>
</dependency>
</dependencies>
----
Note: version `4.0.0+` is requried. Having the adapter on the classpath activates the Azure Java Worker integration.
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation "org.springframework.cloud:spring-cloud-function-adapter-azure:4.0.3"
}
==== Accessing Azure ExecutionContext
----
====
NOTE: version `4.0.0+` is required. Having the adapter on the classpath activates the Azure Java Worker integration.
===== 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.
@@ -97,7 +128,7 @@ public String execute(
HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
Message message = AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context);
Message message = (Message) AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context);
return this.uppercase.apply(message);
}
@@ -117,21 +148,27 @@ public Function<Message<String>, String> uppercase(JsonMapper mapper) {
}
----
==== Notes on JAR Layout
==== Azure JAR Layout
You don't need the Spring Cloud Function Web at runtime in Azure, so you can exclude this before you create the JAR you deploy to Azure, but it won't be used if you include it, so it doesn't hurt to leave it in.
A function application on Azure is an archive generated by the `azure-functions-maven-plugin` Maven plugin.
A function application on Azure is an archive generated either by the Maven (`azure-functions-maven-plugin`) or the Gradle(`azure-functions-gradle-plugin`) plugins.
The function lives in the JAR file generated by this project.
The sample creates it as an executable jar, using the thin layout, so that Azure can find the handler classes. If you prefer you can just use a regular flat JAR file.
The dependencies should *not* be included.
==== Build file setup
In order to run Spring Cloud Function applications on Microsoft Azure, you can leverage the `azure-functions-maven-plugin` Maven plugin offered by the cloud platform provider.
In order to run Spring Cloud Function applications on Microsoft Azure, you have to use Maven or Gradle plugins offered by Azure.
You will need to provide Azure-specific configuration for your application, specifying the `resourceGroup`, `appName` and other optional properties, and add the `package` goal execution so that the `function.json` file required by Azure is generated for you. Full plugin documentation can be found in the https://github.com/microsoft/azure-maven-plugins[plugin repository].
Provide the Azure-specific configuration for your application, specifying the `resourceGroup`, `appName` and other optional properties.
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].
[source,xml]
Sample Azure Function (Maven/Gradle) configuration would like like:
====
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<plugin>
<groupId>com.microsoft.azure</groupId>
@@ -172,22 +209,69 @@ You will need to provide Azure-specific configuration for your application, spec
</plugin>
----
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]
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
plugins {
id "com.microsoft.azure.azurefunctions" version "1.11.0"
// ...
}
Add the `start-class` POM property to point to your main (e.g. SpringApplication) class.
apply plugin: "com.microsoft.azure.azurefunctions"
[source,xml]
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'
}
}
----
====
The complete plugin documentation is available at the https://github.com/microsoft/azure-maven-plugins/tree/develop/azure-functions-maven-plugin[Azure Maven] and https://github.com/microsoft/azure-gradle-plugins/tree/master/azure-functions-gradle-plugin[Azure Gradle] repositories.
Next you must specify the `Start-Class` or `Main-Class` to point to your application main class.
====
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<properties>
<java.version>17</java.version>
<start-class>YOUR MAIN CLASS</start-class>
<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"
)
}
}
----
====
IMPORTANT: The main class provided must be annotated by `SpringBootApplication` or `SpringBootConfiguration` annotation.
You will also have to ensure that the files to be scanned by the plugin can be found in the Azure functions staging directory (see the https://github.com/microsoft/azure-maven-plugins[plugin repository] for more details on the staging directory and it's default location).
Add the `host.json` configuration under the `src/main/resources` folder:
Add the `host.json` configuration file:
[source,json]
----
@@ -200,17 +284,33 @@ Add the `host.json` configuration under the `src/main/resources` folder:
}
----
You can find the entire sample `pom.xml` file for deploying Spring Cloud Function applications to Microsoft Azure with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/pom.xml[here].
TIP: If the file is not in the project top folder you need to configure your plugins accordingly (like `hostJson` maven attribute).
NOTE: As of yet, only Maven plugin is available. Gradle plugin has not been created by
the cloud platform provider.
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)].
==== 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]).
@@ -218,10 +318,20 @@ For some configuration you would need the https://learn.microsoft.com/en-us/azur
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.
@@ -232,10 +342,20 @@ 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.
@@ -273,172 +393,19 @@ VS Code remote debug configuration:
"hostName": "localhost",
"port": "5005"
},
...
]
}
----
==== (Legacy) FunctionInvoker integration option
=== Microsoft Azure Functions Web
The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure
framework into the user functions, using Spring Boot configuration where necessary. Azure Functions has quite a unique and
invasive programming model, involving annotations in user code that are specific to the Azure platform.
However, it is important to understand that because of the style of integration provided by Spring Cloud Function, specifically `org.springframework.cloud.function.adapter.azure.FunctionInvoker`, this annotation-based programming model is simply a type-safe way to configure
your simple java function (function that has no awareness of Azure) to be recognized as Azure function.
All you need to do is create a handler that extends `FunctionInvoker`, define and configure your function handler method and
make a callback to `handleRequest(..)` method. This handler method provides input and output types as annotated method parameters
(enabling Azure to inspect the class and create JSON bindings).
For pure web based function applications, the 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] adapter allows replacing the Azure programming model completely with the familiar Spring Web programming model. You just build your Spring Web app, add the azure-web adapter dependency and the necessarily azure layout packaging.
The `spring-cloud-function-adapter-azure-web` requires the same package layout and build/deployment steps as the `spring-cloud-function-adapter-azure`.
```java
public class UppercaseHandler extends FunctionInvoker<Message<String>, String> {
=== (Legacy) FunctionInvoker integration
@FunctionName("uppercase")
public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET,
HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
Message<String> message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build();
return handleRequest(message, context);
}
}
```
WARNING: The legacy `FunctionInvoker` programming model is deprecated and will not be supported going forward.
Note that aside form providing configuration via Azure annotation we create an instance of `Message` inside the body of this handler method and make a callback to `handleRequest(..)` method returning its result.
The actual user function you're delagating to looks like this
```java
@Bean
public Function<String, String> uppercase() {
return payload -> payload.toUpperCase();
}
OR
@Bean
public Function<Message<String>, String> uppercase() {
return message -> message.getPayload().toUpperCase();
}
```
Note that when creating a Message you can copy HTTP headers effectively making them available to you if necessary.
The `org.springframework.cloud.function.adapter.azure.FunctionInvoker` class has two useful
methods (`handleRequest` and `handleOutput`) to which you can delegate the actual function call, so mostly the function will only ever have one line.
The function name (definition) will be retrieved from Azure's `ExecutionContext.getFunctionName()` method, effectively supporting multiple function in the application context.
==== 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 FunctionInvoker will add an instance of the `ExecutionContext` as a Message header so you can retrieve it via `executionContext` key.
```
@Bean
public Function<Message<String>, String> uppercase(JsonMapper mapper) {
return message -> {
String value = message.getPayload();
ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext");
. . .
}
}
```
==== Notes on JAR Layout
You don't need the Spring Cloud Function Web at runtime in Azure, so you can exclude this
before you create the JAR you deploy to Azure, but it won't be used if you include it, so
it doesn't hurt to leave it in. A function application on Azure is an archive generated by
the Maven plugin. The function lives in the JAR file generated by this project.
The sample creates it as an executable jar, using the thin layout, so that Azure can find
the handler classes. If you prefer you can just use a regular flat JAR file.
The dependencies should *not* be included.
==== Build file setup
In order to run Spring Cloud Function applications on Microsoft Azure, you can leverage the Maven
plugin offered by the cloud platform provider.
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-azure</artifactId>
</dependency>
</dependencies>
----
Then, configure the plugin. You will need to provide Azure-specific configuration for your
application, specifying the `resourceGroup`, `appName` and other optional properties, and
add the `package` goal execution so that the `function.json` file required by Azure is
generated for you. Full plugin documentation can be found in the https://github.com/microsoft/azure-maven-plugins[plugin repository].
[source,xml]
----
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<configuration>
<resourceGroup>${functionResourceGroup}</resourceGroup>
<appName>${functionAppName}</appName>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
----
You will also have to ensure that the files to be scanned by the plugin can be found in the
Azure functions staging directory (see the https://github.com/microsoft/azure-maven-plugins[plugin repository]
for more details on the staging directory and it's default location).
You can find the entire sample `pom.xml` file for deploying Spring Cloud Function
applications to Microsoft Azure with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-sample-azure/pom.xml[here].
NOTE: As of yet, only Maven plugin is available. Gradle plugin has not been created by
the cloud platform provider.
==== Build
----
./mvnw -U clean package
----
==== Running the sample
You can run the sample locally, just like the other Spring Cloud Function samples:
---
./mvnw spring-boot:run
---
and `curl -H "Content-Type: text/plain" localhost:8080/api/uppercase -d '{"value": "hello foobar"}'`.
You will need the `az` CLI app (see https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven for more detail). To deploy the function on Azure runtime:
----
$ az login
$ mvn azure-functions:deploy
----
On another terminal try this: `curl https://<azure-function-url-from-the-log>/api/uppercase -d '{"value": "hello foobar!"}'`. Please ensure that you use the right URL for the function above. Alternatively you can test the function in the Azure Dashboard UI (click on the function name, go to the right hand side and click "Test" and to the bottom right, "Run").
The input type for the function in the Azure sample is a Foo with a single property called "value". So you need this to test it with something like below:
----
{
"value": "foobar"
}
----
NOTE: The Azure sample app is written in the "non-functional" style (using `@Bean`). The functional style (with just `Function` or `ApplicationContextInitializer`) is much faster on startup in Azure than the traditional `@Bean` style, so if you don't need `@Beans` (or `@EnableAutoConfiguration`) it's a good choice. Warm starts are not affected.
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.