Creating Google Cloud Functions With Kotlin
Serverless with Kotlin: Building and Deploying HTTP Functions on GCP
In May 2020, Google announced that Java 11 was coming to Google Cloud Functions.
Naturally, I thought, “Excellent, but can I write functions in other JVM-based languages like Kotlin?”
Thankfully, there is no such limitation. You can write your functions in any JVM language of your choosing.
The source code for this example can be found on GitHub:
https://github.com/mwhyte-dev/kotlin-google-cloud-function
Prerequisites
You’ll need some things to follow along with this example:
Java 11
Gradle
Access to GCP with a sample project created
gcloud installed and authenticated
The source code is cloned and imported into your IDE of choice
Main Function
The main function is a straightforward one to get us started — a basic HTTP endpoint that will return the string “FUNCTION COMPLETE” when triggered:
package dev.mwhyte.function
import com.google.cloud.functions.HttpFunction
import com.google.cloud.functions.HttpRequest
import com.google.cloud.functions.HttpResponse
import mu.KotlinLogging
import java.io.IOException
class App : HttpFunction {
private val logger = KotlinLogging.logger {}
@Throws(IOException::class)
override fun service(request: HttpRequest, response: HttpResponse) {
logger.info { "hello world" }
response.writer.write("FUNCTION COMPLETE")
}
}
This class extends HttpFunction
from the functions-framework library. The service function takes an HttpRequest
and an HttpResponse
object as parameters.
For non-HTTP ways to trigger a cloud function, you can use a RawBackgroundFunction
or a typed variant.
For example: BackgroundFunction<PubSubMessage>
Some other options for triggering functions include Cloud Pub/Sub, Cloud Storage, Cloud Firestore, and various Firebase-based triggers.
You can find examples of other triggering mechanisms in the functions-framework-java readme.
Gradle Build File
We use Gradle’s Kotlin DSL to configure our project for this example.
import java.lang.invoke.MethodHandles.invoker
val invoker by configurations.creating
plugins {
id("org.jetbrains.kotlin.jvm") version "1.3.72"
id("com.github.johnrengelman.shadow") version "6.0.0"
application
}
repositories {
jcenter()
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("io.github.microutils:kotlin-logging:1.11.5")
implementation("com.google.cloud.functions:functions-framework-api:1.0.1")
invoker("com.google.cloud.functions.invoker:java-function-invoker:1.0.0-alpha-2-rc5")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("org.mockito:mockito-core:3.5.10")
testImplementation("com.google.truth:truth:1.0.1")
testImplementation("com.google.guava:guava-testlib:29.0-jre")
}
application {
mainClassName = "dev.mwhyte.function.AppKt"
}
task<JavaExec>("runFunction") {
main = "com.google.cloud.functions.invoker.runner.Invoker"
classpath(invoker)
inputs.files(configurations.runtimeClasspath, sourceSets["main"].output)
args(
"--target", project.findProperty("runFunction.target") ?: "dev.mwhyte.function.App",
"--port", project.findProperty("runFunction.port") ?: 8080
)
doFirst {
args("--classpath", files(configurations.runtimeClasspath, sourceSets["main"].output).asPath)
}
}
tasks.named("build") {
dependsOn(":shadowJar")
}
task("buildFunction") {
dependsOn("build")
copy {
from("build/libs/" + rootProject.name + "-all.jar")
into("build/deploy")
}
}
Dependencies
There are a few critical dependencies.
Functions-framework-API allows us to write lightweight functions that run in many different environments, including Google Cloud Functions and Cloud-run.
Java-function-invoker enables us to run the function locally for testing.
runFunction Task
The runFunction
task in the Gradle build file triggers the java-function-invoker, which wraps the function and serves it using a jetty web server.
To run the function locally, call runFunction using the Gradle wrapper:
./gradlew runFunction
> Task :runFunction
2021-07-13 20:21:00.608:INFO:oejs.Server:main: jetty-9.4.26.v20200117; built: 2020-01-17T12:35:33.676Z; git: 7b38981d25d14afb4a12ff1f2596756144edf695; jvm 15.0.1+9
2021-07-13 20:21:00.646:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@467aecef{/,null,AVAILABLE}
2021-07-13 20:21:00.671:INFO:oejs.AbstractConnector:main: Started ServerConnector@3d24753a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2021-07-13 20:21:00.672:INFO:oejs.Server:main: Started @485ms
Jul 13, 2021 8:21:00 PM com.google.cloud.functions.invoker.runner.Invoker logServerInfo
INFO: Serving function...
Jul 13, 2021 8:21:00 PM com.google.cloud.functions.invoker.runner.Invoker logServerInfo
INFO: Function: dev.mwhyte.function.App
Jul 13, 2021 8:21:00 PM com.google.cloud.functions.invoker.runner.Invoker logServerInfo
INFO: URL: <http://localhost:8080/>
<==========---> 80% EXECUTING [15s]
> :runFunction
Optionally, you can override some args:
./gradlew runFunction -PrunFunction.target=dev.mwhyte.function.App -PrunFunction.port=8080
> Task :runFunction
INFO: Function: dev.mwhyte.function.App
Jul 13, 2021 8:24:24 PM com.google.cloud.functions.invoker.runner.Invoker logServerInfo
INFO: URL: <http://localhost:8080/>
<==========---> 80% EXECUTING [8s]
> :runFunction
buildFunction Task
The buildFunction task in the Gradle build file works with the Gradle Shadow plugin to create a fat jar and copy it to the build/deploy directory. The jar is then ready to be uploaded to GCP Cloud Storage.
To execute this, use the Gradle wrapper:
./gradlew buildFunction
BUILD SUCCESSFUL in 1s
10 actionable tasks: 10 up-to-date
Deploying With gcloud
So you’ve tested your function locally, built it, and now it's time to deploy. The gcloud command-line utility makes deployment easy.
First, ensure you are deploying to the GCP Project of your choice:
gcloud config get-value project my-functions-project
Then select the region you wish to deploy to :
gcloud config set functions/region europe-west1
Lastly, deploy the function:
gcloud functions deploy my-test-function \\\\
--entry-point=dev.mwhyte.function.App \\\\
--source=build/deploy --runtime=java11 --trigger-http \\\\
--allow-unauthenticated
Deploying function (may take a while - up to 2 minutes)...⠹
Note: The entry-point argument is the function's fully qualified class name, and the source is the location of our fat jar.
If you open the GCP console and navigate to Cloud Functions, you’ll see the function:
You can open it here and view various information, including the trigger. Alternatively, you can run a describe
from gcloud:
gcloud functions describe my-test-function
By hitting the trigger URL, you will see the function return the expected response:
curl <https://europe-west1-slice-poc.cloudfunctions.net/my-test-function>
FUNCTION COMPLETE%
References
We’ve seen how easy it is to deploy a Kotlin function to GCP. To explore the topic further, I’d recommend the following documentation:
https://github.com/mwhyte-dev/kotlin-google-cloud-function
Cloud Run functions documentation | Cloud Run functions Documentation | Google Cloud
https://github.com/GoogleCloudPlatform/functions-framework-java
https://github.com/GoogleCloudPlatform/functions-framework-java/issues/35