Microsoft Azure Functions

Azure function adapter for deploying Spring Cloud Function applications as native Azure Java Functions.

The Azure Functions programming model relays, extensively, on Java 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 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.

scf azure adapter
For Web-based function applications, you can replace the generic adapter-azure with the specialized 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 Spring Web programming model instead. For further information follow the 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:

  • Maven

  • Gradle

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-azure</artifactId>
	</dependency>
</dependencies>
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure'
}
version 4.0.0+ is required. Having the adapter on the classpath activates the Azure Java Worker integration.

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 Function Catalog for Spring Cloud Function composition) and use those inside the Azure function handlers.

@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",
				methods = { HttpMethod.POST },
				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",
			methods = { HttpMethod.POST },
			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 @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 Function Catalog composition API.
Use the Java annotations included in the 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:

@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 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 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.

@FunctionName("myfunction")
public String execute(
	@HttpTrigger(name = "req",
		methods = { HttpMethod.POST },
		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:

@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.

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 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.

The Azure packaging format is not compatible with the default Spring Boot packaging (e.g. uber jar). The Disable Spring Boot Plugin section below explains how to handle this.

[[azure-maven/gradle-plugins]] === Azure Maven/Gradle Plugins

Azure provides Maven and 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:

  • Maven

  • Gradle

<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>
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: Java Versions, Deployment OS.

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 Spring Boot Thin Launcher as shown in this Maven snippet:

<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>

Main-Class Configuration

Specify the Main-Class/Start-Class to point to your Spring application entry point, such as the 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:

  • Maven

  • Gradle

<properties>
	<start-class>YOUR APP MAIN CLASS</start-class>
	...
</properties>
jar {
    manifest {
        attributes(
            "Main-Class": "YOUR-APP-MAIN-CLASS"
        )
    }
}
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 App Settings.
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 host.json file to configure the function app.

{
	"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.

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:

Azure Web Adapter

For, pure, Web-based function applications, you can replace the generic adapter-azure with the specialized 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 Spring Web programming model instead.

To enable the Azure Web Adapter, add the adapter dependency to your pom.xml or build.gradle files:

  • Maven

  • Gradle

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-azure-web</artifactId>
	</dependency>
</dependencies>
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure-web'
}

The same Configuration and Usage instructions apply to the Azure Web Adapter as well.

Azure Samples

For further information, explore the following, Azure Web Adapter, sample:

Usage

Common instructions for building and deploying both, Azure Adapter and Azure Web Adapter type of applications.

Build

  • Maven

  • Gradle

./mvnw -U clean package
./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 here). For some configuration you would need the Azurite emulator as well.

Then run the sample:

  • Maven

  • Gradle

./mvnw azure-functions:run
./gradlew azureFunctionsRun

Running on Azure

Make sure you are logged in your Azure account.

az login

and deploy

  • Maven

  • Gradle

./mvnw azure-functions:deploy
./gradlew azureFunctionsDeploy

Debug locally

Run the function in debug mode.

  • Maven

  • Gradle

./mvnw azure-functions:run -DenableDebug
// 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:

{
	"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:

{
	"version": "0.2.0",
	"configurations": [
		{
			"type": "java",
			"name": "Attach to Remote Program",
			"request": "attach",
			"hostName": "localhost",
			"port": "5005"
		},
	]
}

FunctionInvoker (deprecated)

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 azure-sample README and code.