Skip to content

Instantly share code, notes, and snippets.

@dsyer
Last active September 21, 2018 08:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsyer/df03d3eb6ddf8664e1204685dbe62d73 to your computer and use it in GitHub Desktop.
Save dsyer/df03d3eb6ddf8664e1204685dbe62d73 to your computer and use it in GitHub Desktop.
Azure Functions Blog Draft

Spring Cloud Function and Azure Functions

Spring Cloud Function has had support for Microsoft Azure Functions since version 1.0, but in the latest 2.0 releases (still in milestone phase) we decided to change the programming model a bit. This article describes what the changes mean for users, and provides a bit of background behind the shift. We in the Spring team had a lot of fun working on this and collaborating with the folks at Microsoft to get the best blend of the two technologies for our users.

Azure Functions for Java

Microsoft has had Java support in Azure Functions for a while, and it enables developers to easily write and deploy Java code that connects in a serverless way to a wide range of platform services (events, databases, storage, HTTP gateways, etc.) in Azure. It comes with an annotation-based programming model that puts the function implementations in Java methods. So you write a method and annotation it with @FunctionName, and it becomes an Azure Function. There is a rich set of tools based on a Maven plugin (currently) that drives the Azure command line and can be used to build a function, run and debug it locally and deploy it to the cloud. There is a Quickstart Guide on the Azure website which will help you get all the pre-requisites installed and working, and there is more detailed documentation about how Azure Functions works in the Developer’s Guide.

The annotations also tie the function method parameters and return types to the services used at deployment time. For example, if you want to bind to an HTTP gateway at deployment time you use @HttpTrigger:

@FunctionName("uppercase")
public Bar execute(
		@HttpTrigger(name = "req", methods = { HttpMethod.GET,
				HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) Foo foo,
		ExecutionContext context) {
	return new Bar(foo.getValue());
}

In this example we accept an incoming HTTP POST request and Azure binds its body to a POJO of type Foo. We transform the Foo into a Bar and it comes back to the caller in the HTTP response.

HTTP triggers are in the top 5 most popular integrations in Azure Functions, but even more popular are the event-based and storage or database-based triggers. The complete list can be found in the Triggers and Bindings documentation - there is a table where you can click on a specific binding or trigger and it will take you to reference page where there are code samples in all languages, including Java.

Here’s another example using the Azure Event Hub as an input and Cosmos DB as an output. This example is in github:

@FunctionName("uppercase")
public Bar execute(
	@EventHubTrigger(name = "data", eventHubName = "events",
				connection = "TRANSACTIONS_EVENT_HUB_CONNECTION_STRING")
		Foo data,
		@CosmosDBOutput(name = "document", databaseName = "inventory", collectionName = "messages",
				connectionStringSetting = "PRODUCT_ITEMS_DOCUMENTDB_CONNECTION_STRING", createIfNotExists = true)
		OutputBinding<Bar> document,
		final ExecutionContext context) {
	return document.setValue(new Bar(foo.getValue()));
}

The annotations carry connection credential information through an indirection to environment variables that are configured in the function deployment. The configuration for all that happens in the build pom.xml through the Azure Functions Maven plugin. For example:

<plugin>
	<groupId>com.microsoft.azure</groupId>
	<artifactId>azure-functions-maven-plugin</artifactId>
	<configuration>
		<resourceGroup>${functionResourceGroup}</resourceGroup>
		<appName>${functionAppName}</appName>
		<region>${functionAppRegion}</region>
		<appSettings>
            <property>
            	<name>FUNCTIONS_EXTENSION_VERSION</name>
            	<value>beta</value>
            </property>
            <property>
            	<name>TRANSACTIONS_EVENT_HUB_CONNECTION_STRING</name>
            <value>${TRANSACTIONS_EVENT_HUB_CONNECTION_STRING}</value>
            </property>
            <property>
            	<name>PRODUCT_ITEMS_DOCUMENTDB_CONNECTION_STRING</name>
            	<value>${PRODUCT_ITEMS_DOCUMENTDB_CONNECTION_STRING}</value>
            </property>
            <property>
            	<name>MSDEPLOY_RENAME_LOCKED_FILES</name>
            	<value>1</value>
            </property>
		</appSettings>
	</configuration>
	<executions>
		<execution>
            <id>package-functions</id>
            <goals>
            	<goal>package</goal>
            </goals>
		</execution>
	</executions>
</plugin>

In this case the environment variable names link the plugin configuration to the function binding declaration. For instance the @EventHubTrigger has a connection attribute that will be popeulated at runtime from the TRANSACTIONS_EVENT_HUB_CONNECTION_STRING environment variable. The plugin configures it remotely using a local environment variable with the same name (notice the ${} placeholders), which the developer or CI process is responsible for setting up at runtime.

Your own personal connection strings are secrets and can be found in the Azure Dashboard - when you click on the relevant resource there is usually a Connection Strings link (or similar) that you can copy and paste to your local process (e.g. in a script that you run locally but do not check into source control). E.g. you might use a setup-env.sh script like this:

export PRODUCT_ITEMS_DOCUMENTDB_CONNECTION_STRING="AccountEndpoint=https://..."
export TRANSACTIONS_EVENT_HUB_CONNECTION_STRING="Endpoint=sb://..."

and source it once at the beginning of a terminal session.

There are some other plugin declarations in the pom.xml of the sample. They are all important but basically boilerplate - you should be able to copy them and re-use the same configuration in all Azure Function Applications.

Spring Cloud Function

Spring Cloud Function aims to support similar serverless use cases when the application developer declares Spring beans of type java.util.Function. The advantages of using Spring Cloud Function on Azure, as opposed to vanilla Java functions, are that the actual business logic code is (in principle) portable to other platforms, and it is a familiar programming model for existing Spring users. Also, all the usual benefits of Spring apply: dependency injection and comprehensive integration with many other Java libraries.

The equivalent of both the examples above would be a single @Bean:

@Bean
public Function<Foo, Bar> uppercase() {
	return foo -> new Bar(foo.getValue().toUpperCase());
}

In version 1.0 of Spring Cloud Function, the user had to map the Microsoft annotations manually to a JSON deployment descriptor, and wrap it up manually into an archive with the right layout for the platform. The process was brittle (but independent of the Azure Java programming model).

In version 2.0 this would still work, but we have chosen to support the use of the Azure annotations a bit more explicitly. So now we have a base class that application developers can extend and decorate with the Azure annotations. The example above would be exactly the same @Bean and one of the execute methods above would be inserted into the subclass of the Spring Cloud handler. Example:

public class UppercaseHandler extends AzureSpringBootRequestHandler<Foo, Bar> {
	@FunctionName("uppercase")
		@HttpTrigger(name = "req", methods = { HttpMethod.GET,
				HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) Foo foo,
		ExecutionContext context) {
	return super.handle(foo, context);
}

Notice that the base class AzureSpringBootRequestHandler is generic with type parameters for input and output. There are 2 handle* methods in the base class, one (handle) which returns the response object, and one (handleOutput) which accepts an OutputBinding and binds it to the output from the Function.

There are various configuration options that drive the runtime behaviour of the Azure Function. The most important (and only mandatory) one is the MAIN_CLASS, which is the main @SpringBootApplication class that carries the declaration of the Function (or Functions). You can specify this as an environment variable, or as the Main-Class entry in the application jar manifest (this is the way it is set up in the sample below). As long as your app has a main class with precisely one function, there is no need to do anything else. In the sample app linked above we use the manifest to define the main class:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<mainClass>example.FunctionApplication</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>

If your app has multiple Function beans, they can be mapped to the Azure function name though the @FunctionName annotation - the bean name matches the function name. In this way you can create an Azure Function Application, which is a single deployment artifact for a group of functions. If you prefer, you can also use an arbitrary @FunctionName and configure the Spring Cloud Function name through an environment variable FUNCTION_NAME.

There is another simple sample of how to set up a Spring Cloud Function as an Azure Function in the project repo - this one is an HTTP trigger from an Azure perspective, but the Spring Cloud Function parts are very similar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment