I recently posted a tweet in which I briefly mentioned that I'd been writing a simple Azure Function using Kotlin. I thought I'd put together something a bit more in-depth, and which did something a bit more than say hello back based on a name in the query string (although I do cover this below, sorry).
This is a first post on a sample project I put together, covering pre-requisites and some of the basics.
Prerequisites
I've written the solution using JetBrains IntelliJ and used the Azure Functions Maven Archetype, which works really nicely so it's not really cheating. In order to get started you'll need the following.
JDK (1.8)
.NET Core SDK (latest)
.Azure Functions Core Tools (2.0 and above)
Maven is required as well but IntelliJ takes care of that part.
Creating a new project
Creating a new project using the archetype is pretty easy, but you'll need to add it first which you can do using the "Add Archtype" option and the details provided.
Once you've created your project you'll need to configure Kotlin in the project. The temptation after doing this is to delete the "java" language folders and create new "kotlin" ones, but don't. The Azure Function plugin references code in here so you'll need to keep it, but not necessarily the code.
Before you start writing any code you'll first need to set up your configuration for running locally. When running in Azure your configuration is available as environment variables, to replicate this locally you can add your configuration to the "local.settings.json" file, these are added to your environment when you run your code. The plugin pre-defines some for you but you'll need specify your storage account settings. If you're running on Windows then you can use the Azure Storage Emulator, unfortunately this isn't available outside of Windows so you may need to provision a storage account in Azure to do this. If you've logged in using the Azure CLI tools then you can execute the following to create a storage account within a resource group.
# Create a resource group
az group create \
--name kotlin-demo-rg \
--location northeurope
# Create the storage account
az storage account create \
--resource-group kotlin-demo-rg \
--name demostore20181103 \
--encryption-services blob \
--encryption-services table \
--encryption-services file \
--https-only true \
--kind StorageV2 \
--location northeurope \
--sku Standard_LRS
These commands will create a resource group and storage account with encryption enabled for blob, table and file stores, with HTTPS only access and locally redundant storage. For storage accounts supporting Azure Functions you can choose Locally or Globally redundant storage, but not Zone redundant.
Once you have your storage account, emulated or real, you can set you configuration to point to it. Here I've used the storage emulator as I'm on Windows, with this you can use a special value to indicate that local storage should be used.
Some code
Let's start with a quick function definition for an HTTP triggered function which accepts a GET request. We need to start with a class and a single function.
class ExampleFunction {
@Function("ExampleFunction")
fun run (
@HttpTrigger(name="request", methods=[HttpMethod.GET], authLevel=AuthorizationLevel.ANONYMOUS)
request: HttpRequestMessage<String?>,
executionContext: ExecutionContext): HttpResponseMessage {
val logger = executionContext.logger
logger.info("Executing example Kotlin function")
val name = request.queryParameters.getOrDefault("name", "Bob")
return request.createResponseBuilder(HttpStatus.OK)
.body("Hello $name")
.build()
}
}
This creates a simple function which is invoked via an HTTP trigger (HTTP call), looks for a query parameter called "name" and returns a greeting (or the name Bob if it's not been specified) back to the caller.
When you're ready to execute it you can clean and package the project as usual using Maven, but the archetype also adds in some new ones, for this part it's the "azure-functions:run" option we want.
Executing this will run our function locally using the Azure Functions CLI tools. Assuming everything has been built correctly then you should be given a URL the function is listening on which you can call like this (using Postman, VSCode or the REST client in IntelliJ)
GET: http://localhost:7071/ExampleFunction?name=Ada
At this stage I'm mostly going over the same content as the Microsoft documentation which is great for getting you up and running, but once you want to do more than just use Strings things get a little more complicated.
For this solution I wanted to create a solution which could accept messages, log them, and then return them when requested. To do this I created 2 functions, one accepting a POST request with an object to persist which contains a fictional asset message, then a second which returns all messages logged for an asset. The data store to keep this simple (although it's a very useful service) is Azure Table Storage, with a partition key based on the ID of the asset.
The following is an example of the object being provided to the POST function.
{
"assetId": "00001",
"messageId": "0010012",
"message": "Something bad happened",
"loggedDate": "2018-11-02T13:12:09Z"
}
Accepting the object should be straight forward, Functions will try and match the request body to the request parameter type, if it matches then the object is hydrated with the values, if it doesn't match and no other function accepts it then an error 500 is returned. I struggled to get this to work, most likely because I'm fairly new to Kotlin, so fell back to an object with string values and then converted in code.
class LogDataEntity constructor() : TableServiceEntity() {
var message: String = ""
var loggedDate: Date = DateTime.parse("1970-01-01").toDate()
constructor(assetId: String, messageId: String) : this() {
this.partitionKey = assetId
this.rowKey = messageId
}
constructor(source: LogData) : this(source.assetId, source.messageId) {
message = source.message ?: ""
loggedDate = DateTime.parse(source.loggedDate).toDate()
}
}
Table Storage doesn't currently support java.time.Instant types so I had to fall back to using java.util.Date, because of this I pulled in Joda Time to make working with them easier.
Once the code is up and running you can then use another archetype option to deploy, the "azure-functions:deploy" option. Before you do though you need to open up the pom.xml file and change a few options. In the properties section there are three settings.
functionAppName
functionAppRegion
functionResourceGroup
The resource group name can be anything you want (or the same as the one you created using the script above), the app name needs to be globally unique which is why it auto-generates the timestamped name, the region should be one where the service is available.
Once deployed you'll need to set any missing function app settings (note, you'll need to do this each time you deploy using this method), and then get the function URL (if you've opted for function level authentication).
From that you can then try calling the deployed function. With the functions I have running, after calling the POST function a couple of times I was able to call the GET function and retrieve the following from it.
[
{
"assetId": "00001",
"messageId": "0010012",
"message": "Something bad happened",
"loggedDate": "Fri Nov 02 13:12:09 GMT 2018"
},
{
"assetId": "00001",
"messageId": "0010013",
"message": "Something less bad happened",
"loggedDate": "Sat Nov 03 02:47:11 GMT 2018"
}
]
Obviously I still have a little tidying up to make sure the date and times come back as expected, but it shows it working for now.
Up Next
If you want to dive into the code you can do over at Github. In the next post I'll start diving into the code for the function and working with Azure Table Storage.