Ballerina

I’ve been planning to take a look at Ballerina for a year or so now. Ballerina bills itself as a “Cloud Native Programming Language”. It is designed to make it easy to write microservices with integrated APIs. Ballerina is an open source, compiled, statically and strongly typed, language. If you’ve written any Java and, to a lesser extent, Go, it’s going to feel familiar. It comes with a concurrent execution model and is built with a set of pluggable imports for systems integration, for example plugging in third-party APIs like Twitter or adding a database backend. One of the more interesting aspects of the language is native types for JSON, XML and Table-based data, designed to make it easier to handle the formats that are the lingua franca of the API ecosystem.

TIP   There’s a lot of useful information about Ballerina’s background and what’s happening with the project in the FAQ

This post walks through some basic Ballerina. It’s not opinionated on whether Ballerina is a good choice for you or not: you should do your own requirements analysis for that. :)

Hello World (API) πŸ”—︎

Ballerina focusses on quickly building services. Let’s take a quick look at a (functional) snippet of Ballerina that is the Hello World (API edition) of the language:

import ballerina/http;

endpoint http:Listener listener {
    port: 9090
};

service<http:Service> hello bind listener {
    sayHello(endpoint caller, http:Request request) {
        http:Response response = new;
        response.setTextPayload("Yo James!\n");
        _ = caller->respond(response);
    }
}

First, we’ve imported Ballerina’s HTTP package. This contains all the pieces needed to build an HTTP endpoint for a service.

Next, we’ve created a HTTP listener endpoint on port 9090. It has a service, how Ballerina describes a network-addressable API, this one exposed on the /hello path, bound to that listener. Inside that service, we have a resource, an invokable API method, called sayHello, available on the path /hello/sayHello. The method has a caller, the client invoking the resource and a request object representing a HTTP request.

Inside the resource, we define an object, response, to hold our HTTP response and return that to the caller. We then call a function, setTextPayload, on the response object to set its payload as Yo James!\n. Finally, we pass that response to the caller:

_ = caller->respond(response);

Here _ ignores any errors and our response is delivered via a synchronous network-bound call, indicated by the ->.

Pretty neat in all of 13 lines of code.

Let’s install Ballerina and run the example.

Installing Ballerina πŸ”—︎

You can get Ballerina on the Downloads page. There are installer packages for Windows, Mac OS, and Linux. I’m running Linux, so I am just going to download and install the Deb package.

$ wget https://product-dist.ballerina.io/downloads/0.980.1/ballerina-platform-linux-installer-x64-0.980.1.deb
$ sudo dpkg -i ballerina-platform-linux-installer-*.deb

The package provides a ballerina binary.

$ ballerina version
Ballerina 0.980.1

Ballerina is currently pre-1.0, with the associated “here be dragons”, but the last few releases have been locking down the language and other features.

Running Hello World (API) πŸ”—︎

Now let’s copy our Hello World code into a file:

$ touch hello_service.bal

Ballerina code has the file extension, .bal, and generally files are named for the service they contain. Paste our code into this file and we can run it with Ballerina.

$ ballerina run hello_service.bal
ballerina: initiating service(s) in 'hello_service.bal'
ballerina: started HTTP/WS endpoint 0.0.0.0:9090

TIP   There’s also Ballerina plugins for VSCode and IntelliJ on the Downloads page if you use either editor. Both plugins are excellent and make development a lot smoother. Ballerina also has its own graphical IDE, called Composer, that’s fairly cool and well worth a look. There are a lot of neat visualizations of program flow. It’s installed with Ballerina and can be run with the composer binary.

This will run our Ballerina program, binding it to local interfaces on port 9090. If we curl that socket on the /hello/sayHello path we’ll see:

$ curl localhost:9090/hello/sayHello
Yo James!

Let’s create a more sophisticated example.

Fortunes favor πŸ”—︎

We’re going to create a new service with two API resources that add and return fortunes.

We’re going to create a new project for this service. Ballerina has an init mode to create a new project.

$ mkdir ballerina_fortune && cd ballerina_fortune
$ ballerina init
Ballerina project initialized

This will create a new Ballerina project with a template Hello world service, configuration file Ballerina.toml, and a .gitignore file.

NOTE   It’s not a Git repository though, you’ll need to run git init in the directory to initialize a repository.

The Ballerina.toml file contains some metadata for your project.

[project]
org-name = "jamtur01"
version = "0.0.1"

The org-name defaults to your username. Ballerina uses the org-name as namespacing. Ballerina programs can be packaged, shared via the Ballerina Central site and import-ed into your programs. This is how you can add third-party APIs for which someone has created a package, for example adding support for the Twitter API.

Ballerina comes with a search sub-command to find packages you might be interested in on Central.

$ ballerina search twitter

Ballerina Central
=================
|NAME            | DESCRIPTION                   | DATE           | VERSION |
|----------------| ------------------------------| ---------------| --------|
|wso2/twitter    | Connects to Twitter from Ba...| 2018-07-17-Tue | 0.9.16  |
|ketharan/twit...| Twitter integration using b...| 2018-07-03-Tue | 0.0.1   |

Now let’s create our service.

Creating a fortunes service πŸ”—︎

We’re going to delete the template hello_service.bal file that has been auto-created.

$ rm hello_service.bal

And create a new service in a file called fortunes_service.bal

$ touch fortunes_service.bal

And then populate this file with the following code.

import ballerina/http;

endpoint http:Listener listener {
    port:9090
};

string[] fortunes = [ 
    "In specially marked packages only.",
    "I am myself plus my circumstance, and if I do not save it, I cannot save myself.",
    "You will be traveling and coming into a fortune.",
    "He who knows, does not speak. He who speaks, does not know.",
    "Friction is a drag."
];

@http:ServiceConfig { basePath: "/fortunes" }
service<http:Service> fortune bind listener {

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/"
    }
    getFortune(endpoint client, http:Request req) {
        json payload = check <json>fortunes;
        http:Response response;
        if (payload == null) {
            payload = "No fortunes found.";
        }
        
        response.setJsonPayload(payload);
        
        _ = client->respond(response);
    }

    @http:ResourceConfig {
        methods: ["POST"],
        path: "/"
    }
    addFortune(endpoint client, http:Request req) {
        string fortuneReq = check req.getTextPayload();
        fortunes[lengthof fortunes] = fortuneReq;
        
        string payload = "Fortune added - " + fortuneReq;
        http:Response response;
        response.setTextPayload(untaint payload);
        response.statusCode = 201;
       
        _ = client->respond(response);
    }
}

You can see we’ve created an expanded and extended version of our original hello world service. We’ve again created an HTTP endpoint listening on port 9090.

We’ve also created an array of strings, originally named fortunes, to hold our fortunes. When we add fortunes we’ll append to this array, but as we have no permanent data store, new fortunes will vanish when Ballerina stops running the service.

We’ve next created our service, prefixing it with:

@http:ServiceConfig { basePath: "/fortunes" }

The @http is an annotation on the HTTP package and defines some metadata for the service that follows, here the base path /fortunes. Annotations exist in several packages and can be attached to services, resources, functions and a variety of other items.

TIP   You can also create your own annotations.

We’ve then created a service and bound it to our listener. Inside the service we’ve created two resources, again prefixed with annotations that configure the verb and the path our resources are available on. We have resources for GET and POST, both on the base path of /fortunes/.

Each resource has a method, getFortune and addFortune respectively, which get all our fortunes as a JSON response and adds a fortune.

Let’s run our program now.

$ ballerina run fortunes_service.bal
ballerina: initiating service(s) in 'fortunes_service.bal'
ballerina: started HTTP/WS endpoint 0.0.0.0:9090

Let’s start by curl our existing fortunes.

$ curl http://localhost:9090/fortunes
["In specially marked packages only.","I am myself plus my circumstance, and if I do not save it, I cannot save myself.","You will be traveling and coming into a fortune.","He who knows, does not speak. He who speaks, does not know.","Friction is a drag."]

And we’ve returned our fortunes as a JSON array. We can also add a fortune.

$ curl -d 'This is a fortune too.' http://localhost:9090/fortunes
Fortune added - This is a fortune too.

We’ve added our fortune as a string but we could easily change the program to take JSON or XML input instead. Now if we return the list of fortunes again,

$ curl http://localhost:9090/fortunes
["In specially marked packages only.","I am myself plus my circumstance, and if I do not save it, I cannot save myself.","You will be traveling and coming into a fortune.","He who knows, does not speak. He who speaks, does not know.","Friction is a drag.","This is a fortune too."] 

And now we can see our added fortune!

TIP   There are a bunch of really useful Ballerina examples you can crib from in the Ballerina by Guide section.

Deploying fortunes πŸ”—︎

Running the ballerina binary to run the service each time isn’t manageable as a deployment. So Ballerina comes with a number of ways to deploy programs. The easiest way though is to turn our Ballerina program into a Docker image. To do this we can enable Docker support inside our Ballerina program.

TIP   You can also create linked binaries from Ballerina programs using the ballerina build programs. Also available is support for command line configuration options using the ballerina/config package.

Let’s do that now:

import ballerina/http;
import ballerinax/docker;

@docker:Config {
    registry:"jamtur01",
    name:"fortunes",
    tag:"v1.0"
}

@docker:Expose{}

endpoint http:Listener listener {
    port:9090
};

. . .

You can see we’ve added a new package, ballerinax/docker, to our list of imports.

TIP   See the x at the end there, thinking that is a typo has thrown me a couple of times.

This package contains support for working with Docker images. We’ve next created an annotation to configure our Docker image, specify a registry (which equates to a Docker registry), a name for the image and any image tags. We also set another annotation to expose our listener port. Ballerina’s Docker support provides many of the configuration options you’re used to when building your own Docker images

Now, if we run the ballerina build command, Ballerina will make use of the Docker configuration and generate an image.

$ ballerina build fortunes_service.bal
Compiling source
  fortunes_service.bal

Generating executable
  ./target/fortunes_service.balx
    @docker                  - complete 3/3

    Run following command to start docker container:
    docker run -d -p 9090:9090 jamtur01/fortunes:v1.0

The fortunes_service.bal will be compiled into an executable and then embedded into a Docker image, in our case jamtur01/fortunes:v1.0.

NOTE   This assumes y’all have Docker installed.

We can then run that Docker image using the docker binary.

$ docker run -d -p 9090:9090 jamtur01/fortunes:v1.0
ff109a7f62f13f5fc7b9fb701081ba5af8bfd706bffafaf324c700fd0ff31ffa

And then we can see the container running.

$ docker ps
CONTAINER ID IMAGE                  COMMAND                CREATED        STATUS        PORTS
ff109a7f62f1 jamtur01/fortunes:v1.0 "/bin/sh -c 'balleri…" 38 seconds ago Up 37 seconds 0.0.0.0:9090->9090/tcp

And add a fortune.

$ curl -d 'This is a fortunate fortune.' http://localhost:9090/fortunes
Fortune added - This is a fortunate fortune.

We could then push this image to a registry and deploy it somewhere. This build process allows you to pretty easily integrate Ballerina into your CI/CD pipeline without a lot of additional configuration or overhead required.

Want to learn more? πŸ”—︎

I hope that’s been a useful introduction to Ballerina. If you’re interested in learning a bit more about Ballerina, they have some of the best documentation I’ve seen in a young project. Their Learn section has some excellent resources and the Help section can connect you with other folks working with Ballerina.

comments powered by Disqus