In this tutorial, we will be experimenting a new use case of the great Azure Functions service 😁 one of my favorite products in Azure.
In this tutorial, we will bring the powerful features of the Spring Framework to our Azure Functions Java projects. After this tutorial, creating a new Azure Functions Java based on business logic that you already have in your Spring Boot Application will be a very easy game.
Before digging into the code, we need to create a new Functions Application in our Azure subscription.
Creating the Azure Function App#
The first step is to create the new Functions Application on the Azure Portal: https://portal.azure.com
Next, go to the Function App and when you click on Add you will get this window:
Create new Azure Function
Fill this form with this details:
- App name: spring-azure-function
- Resource Group: spring-azure-function-rg
- OS: Windows
- Hosting Plan: Consumption plan
- Location: France Central
- Runtime Stack: Obviously Java 😁
- Storage: We will create a new one
- Application Insights: Just click on Application insights and Enable the Collect application monitoring data using Application Insights. Then Apply and next click on Create 🤓
PREPARING the Functions Application POM.XML#
We will start by generating a new Spring Boot application using the Spring Initializr:
Generate a blank new Spring Boot application using Spring Initializr
Next; in the generated project, in the pom.xml
replace the properties
section with these values:
1
2
3
4
5
6
7
8
9
10
11
12
13
| <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<azure.functions.maven.plugin.version>1.3.4</azure.functions.maven.plugin.version>
<azure.functions.java.library.version>1.3.4</azure.functions.java.library.version>
<functionAppName>spring-azure-function</functionAppName>
<functionAppRegion>francecentral</functionAppRegion>
<stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
<functionResourceGroup>spring-azure-function-rg</functionResourceGroup>
<start-class>com.targa.labs.dev.springfunctionsazure.SpringFunctionsAzureApplication</start-class>
<wrapper.version>1.0.23.RELEASE</wrapper.version>
</properties>
|
In this properties, we have:
functionAppName
: the name of the Functions Application that we already created in our Azure SubscriptionfunctionAppRegion
: the location defined for the Functions Application that we already created in our Azure Subscriptionstart-class
: the full path of the main class of the generated Spring Boot application
Next, in the dependencies
section, we need to replace the listed ones by these values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| <dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
|
In this code we added:
- Spring Cloud Starter Function Web: brings in the necessary dependencies to run the functions locally.
- Spring Cloud Function Adapter Azure: bootstraps a Spring Cloud Function context and channels function calls from the Azure framework into the user functions, using Spring Boot configuration where necessary.
- Lombok: Java library that will help us avoid writing the getters, setters, toString, constructors..
- Spring Boot Starter Test: contains the majority of dependencies and configurations required for the tests.
Add to that, we need to add this dependencyManagement
section:
1
2
3
4
5
6
7
8
9
10
11
| <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>2.0.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
|
This dependencyManagement
section will make our project inherit dependencies information from the Spring Cloud Function Dependencies parent pom.
Now, we will prepare the build
section of our pom.xml
. In this section we will need to add and configure many plugins:
- Azure Functions Maven Plugin
- Maven Resources Plugin
- Maven Dependency Plugin
- Maven Clean Plugin
- Spring Boot Maven Plugin
We will define the plugins that we will have in the pluginManagement
section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| <pluginManagement>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</pluginManagement>
|
Yes 😆 I didn’t mentioned the plugin spring-boot-maven-plugin
in the pluginManagement
section for a very simple reason: our project is a Spring Boot Application and it will inherit the details of the spring-boot-maven-plugin
from the Spring Boot Parent Pom 🤓
Now we will configure the build plugins one by one. Let’s start by azure-functions-maven-plugin
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| <plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<configuration>
<resourceGroup>${functionResourceGroup}</resourceGroup>
<appName>${functionAppName}</appName>
<region>${functionAppRegion}</region>
<appSettings>
<!-- Run Azure Function from package file by default -->
<property>
<name>WEBSITE_RUN_FROM_PACKAGE</name>
<value>1</value>
</property>
<property>
<name>FUNCTIONS_WORKER_RUNTIME</name>
<value>java</value>
</property>
<property>
<name>MAIN_CLASS</name>
<value>com.targa.labs.dev.demo.SpringFunctionsAzureApplication</value>
</property>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~2</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
|
We passed the Azure Functions properties that we defined in a previous step. We also specified some Settings for our Azure Functions Application:
WEBSITE_RUN_FROM_PACKAGE
: 1 👉 True to run Azure Function from package fileFUNCTIONS_WORKER_RUNTIME
: Java 🤔 ObviouslyMAIN_CLASS
: the full path name of the Main class of our Spring Boot ApplicationFUNCTIONS_EXTENSION_VERSION
: ~2 👉 the Azure Functions Extension Version, we will be using the latest stable version but you can use the ~3 to try to the preview of the incoming 3.x
Now, we will configure the maven-resources-plugin
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| <plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/src/main/azure
</directory>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
<outputDirectory>
${project.build.directory}/azure-functions/${functionAppName}
</outputDirectory>
<overwrite>true</overwrite>
</configuration>
</execution>
</executions>
</plugin>
|
The Maven Resources Plugin will copy all the resources available under the folder src/main/azure
into the folder target/azure-functions/spring-azure-function
.
Good, but we don’t have this folder yet, so it’s a good opportunity to create it. Inside this folder, we need to create a host.json
file that contains:
1
2
3
4
5
6
7
| {
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
|
and we need to create local.setting.json
file that contains:
1
2
3
4
5
6
7
| {
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "java"
}
}
|
Great ! Now let’s define the configuration maven-dependency-plugin
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${stagingDirectory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
|
The Maven Dependency Plugin will copy all the dependencies for running the Azure Function Application into to the folder target/azure-functions/spring-azure-function/lib
.
Now we need to configure the Maven Clean Plugin to remove the obj
folder generated by the Azure Functions DotNet SDK:
1
2
3
4
5
6
7
8
9
10
11
| <plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<filesets>
<fileset>
<directory>obj</directory>
</fileset>
</filesets>
</configuration>
</plugin>
|
Finally, it’s the turn of the Spring Boot Maven Plugin:
1
2
3
4
5
6
7
8
9
10
11
| <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>
|
Here as you see, we added a dependency within the Spring Boot Maven Plugin which is the Spring Boot Thin Layout which helps to dry run (download and cache the dependencies) the Spring Boot built JAR 🥳
CODING THE FUNCTIONS APPLICATION#
Let’s create a sample Function that gets a User object with Name attribute and makes as output a Greeting object with “Hello Name” message attribute.
The User class:
1
2
3
4
5
6
7
8
| import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private String name;
}
|
The Greeting class:
1
2
3
4
5
6
7
8
| import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Greeting {
private String message;
}
|
Now, we can create our sample Function class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class GreetingHandler extends
AzureSpringBootRequestHandler<User, Greeting> {
@FunctionName("greet")
public Greeting execute(
@HttpTrigger(name = "request",
methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
context.getLogger().info(
String.format("Request for : [%s]",
request.getBody().map(User::getName))
);
return handleRequest(request.getBody().get(), context);
}
}
|
Our GreetingHandler
class inherits from AzureSpringBootRequestHandler<InputTypeClass, OutputTypeClass>
. The AzureSpringBootRequestHandler
class has a main method called handleRequest
to which we will use to delegate the actual function call that we received in execute()
method to the Spring Context, which will forward our call to the suitable configured Spring Bean.
Cool, but where our function call will be forwarded ?
We want that our request get computed in a Spring Service component which looks like this:
1
2
3
4
5
6
7
| @Service
public class GreetingService {
public Greeting sayHello(User name) {
return new Greeting("Hello " + name);
}
}
|
Now, we need to make the glue that links our Function Handler to the Spring Service 🤔
It’s quite easy ! to do that, we just need to define a Bean
of type Function
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @SpringBootApplication
public class SpringFunctionsAzureApplication {
@Autowired
private GreetingService greetingService;
@Bean
public Function<User, Greeting> hello() {
return user -> greetingService.sayHello(user);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringFunctionsAzureApplication.class, args);
}
}
|
Now, our function will forward the call to the sayHello()
method of the GreetingService
that we injected 🥳
Let’s build and run our Spring Booted Azure Function 🤪
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| $ mvn clean package azure-functions:run
...
[INFO] --- azure-functions-maven-plugin:1.3.4:run (default-cli) @ spring-functions-azure ---
..
[INFO] Azure Functions Core Tools found.
%%%%%%
%%%%%%
@ %%%%%% @
@@ %%%%%% @@
@@@ %%%%%%%%%%% @@@
@@ %%%%%%%%%% @@
@@ %%%% @@
@@ %%% @@
@@ %% @@
%%
%
Azure Functions Core Tools (2.7.1846 Commit hash: 458c671341fda1c52bd46e1aa8943cb26e467830)
Function Runtime Version: 2.0.12858.0
..
[19/11/2019 20:33:36] Generating 1 job function(s)
[19/11/2019 20:33:36] Found the following functions:
[19/11/2019 20:33:36] Host.Functions.greet
[19/11/2019 20:33:36]
[19/11/2019 20:33:36] Initializing function HTTP routes
[19/11/2019 20:33:36] Mapped function route 'api/greet' [POST] to 'greet'
[19/11/2019 20:33:36]
[19/11/2019 20:33:36] Host initialized (213ms)
[19/11/2019 20:33:36] Host started (218ms)
[19/11/2019 20:33:36] Job host started
[19/11/2019 20:33:36] Listening for transport dt_socket at address: 5005
Http Functions:
greet: [POST] http://localhost:7071/api/greet
Hosting environment: Production
..
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
[19/11/2019 20:33:43] Host lock lease acquired by instance ID '00000000000000000000000014D8753A'.
|
To consume our Function, just POST a User Payload to the shown URL:
1
| $ curl http://localhost:7071/api/greet -d "{\"name\":\"Nebrass\"}"
|
The response will look like:
1
2
3
| {
"message": "Hello Nebrass"
}
|
Cool ! Our HTTP Request is now propagated from Azure Functions Host to the Spring Boot Context 🥳
HTTP Request propagation from Azure Functions to Spring Context
We can now go further to do more tasks. We can for example make some advanced operations like inserting data to CosmosDB from the Spring Service and based on the Azure Functions Connection Strings 😁
Accessing Cosmos DB from Spring Service#
Now, just add this Spring Data CosmosDB dependency to our pom.xml
:
1
2
3
4
5
| <dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>spring-data-cosmosdb</artifactId>
<version>2.2.1.M1</version>
</dependency>
|
Now, we need to create the CosmosDbConfiguration
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @Configuration
@EnableCosmosRepositories(basePackageClasses = StudentRepository.class)
@Slf4j
public class CosmosDBConfiguration extends AbstractCosmosConfiguration {
@Value("${CosmosDbConnection}")
private String key;
@Value("${azure.cosmosdb.database}")
private String dbName;
@Bean
public CosmosDBConfig getConfig() {
CosmosDBConfig cosmosdbConfig = CosmosDBConfig.builder(key, dbName).build();
log.error("key is {}", key);
log.error("dbName is {}", dbName);
return cosmosdbConfig;
}
}
|
The @Value("${CosmosDbConnection}")
will grab the value from the System Environment Variables. This value will be injected in the application environment from the Azure Functions Runtime based on our configuration in the Azure Portal or from the local.settings.json
file when working locally.
The @Value("${azure.cosmosdb.database}")
will grab this value from the application.properties
- so let’s add it, it will be our CosmosDB Database Name, which I called university
:
1
| azure.cosmosdb.database=university
|
The @EnableCosmosRepositories(basePackageClasses = StudentRepository.class)
annotation will inject the CosmosDbConfiguration
that we already created in the StudentRepository
class that we need to create now 😁
1
2
3
4
| @Repository
public interface StudentRepository extends CosmosRepository<Student, String> {
List<Student> findByName(String name);
}
|
It’s a very classic Spring Data Repository interface 😆nothing special ! and this is the beauty of Spring Boot !
Now we can inject our StudentRepository
in any Spring Service class and enjoy its powerful features:
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Service
@Slf4j
public class StudentsService {
@Autowired
private StudentsRepository studentsRepository;
public void countUsers() {
List<User> studentsList = new ArrayList<>();
this.studentsRepository.findAll().forEach(studentsList::add);
log.info("We have {} records in the Students DB", studentsList.size());
}
}
|
So simple !! In this way, you can enjoy all the features and the facilities of the Spring Boot in the Azure Functions Java projects !
Have fun ! 🥳