heidloff.net - Building is my Passion
Post
Cancel

Developing Polyglot Serverless Applications

As serverless platforms mature, more and more sophisticated cloud-native applications are built with serverless technologies. These applications are assembled with potentially many functions that are loosely-coupled and can be developed by different teams.

Teams might choose different languages for the following reasons:

  • Reuse existing code or libraries
  • Leverage available skills in the team
  • Implement more efficient functions

In this article I’m not going to describe how to implement the most efficient functions and when to use which programming language, but certain languages may be better for specific requirements. Some languages might be faster to start, faster to execute code at runtime, require less memory, etc.

I’ve open sourced a sample that shows how to use JavaScript, TypeScript, Java and Kotlin together in a serverless application.

Get the code from GitHub.

The polyglot ‘send notification’ application sends either emails or text messages to certain users, depending on the information provided in their profiles. Here is the data flow used in this sample, as visualized by OpenWhisk Composer.

  • The application is triggered with a person ID and a subject.
  • The function ‘read-person-profile’ returns the profile for the specific user. The profile contains email addresses and can contain phone numbers.
  • If the profile has a phone number, the function ‘send-text-message’ is invoked, otherwise the application proceeds to the ‘send-mail’ function.
  • The other functions transfer the data in the format expected by the loosely coupled business logic functions.

image

The sample uses IBM Cloud Functions which is based on Apache OpenWhisk. While the functions are stateless, applications defined with OpenWhisk Composer manage the state and are responsible for how the data flows between functions. Read my previous article to learn more about the data flows and function schemas.

OpenWhisk supports several different programming languages out of the box: JavaScript, Java, Python and more. You can also use Docker to implement functions, which is what I’m using in this sample.

Docker provides some advantages that might be important for your requirements:

  • You can develop and test the same Docker image locally, which is later deployed and run in the cloud. This minimizes the chance of running into issues due to different environments.
  • You can choose the programming languages that your cloud functions provider does not support out of the box.
  • You can use new run-time versions sooner than your cloud functions provider might support, for example use Java 9 today in OpenWhisk.

Here are links to the code how to implement OpenWhisk functions with Docker and different languages.

This is the Kotlin function used in the sample. Essentially only one ‘/run’ endpoint needs to be implemented.

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

import io.javalin.Javalin
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

data class InputObject(val phone: String  = "", val subject: String  = "")
@JsonIgnoreProperties(ignoreUnknown = true)
data class InputFunction(val input: InputObject = InputObject())
@JsonIgnoreProperties(ignoreUnknown = true)
data class InputBody(val value: InputFunction = InputFunction())

data class OutputObject(val phone: String = "", val subject: String = "", val ok: Boolean = true)
data class OutputFunction(val outputsendtextmessage: OutputObject = OutputObject())

fun main(args: Array<String>) {
    val app = Javalin.create().apply {
        port(8080)
        exception(Exception::class.java) { e, ctx -> e.printStackTrace() }
    }.start()

    app.post("/init") { ctx -> ctx.status(200) }

    app.post("/run") { ctx ->
        val body = ctx.bodyAsClass(InputBody::class.java)
        val inputFunction = body.value
        val inputObject = inputFunction.input
        val phone = inputObject.phone
        val subject = inputObject.subject

        // real implementation goes here

        val outputObject = OutputObject(phone, subject)
        val outputFunction = OutputFunction(outputObject)

        ctx.json(outputFunction)
        ctx.status(200)
    }
}

Want to run this sample? Try it out on the IBM Cloud; get an account here.

Featured Blog Posts
Disclaimer
The postings on this site are my own and don’t necessarily represent IBM’s positions, strategies or opinions.
Trending Tags