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.
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 versionBallerina 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.balballerina: initiating service(s) in 'hello_service.bal'ballerina: started HTTP/WS endpoint 0.0.0.0:9090
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/sayHelloYo 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 initBallerina project initialized
This will create a new Ballerina project with a template Hello world
service, configuration file Ballerina.toml
, and a .gitignore
file.
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.
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.balballerina: 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/fortunesFortune 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!
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.
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.
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.balCompiling 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
.
We can then run that Docker image using the docker
binary.
$ docker run -d -p 9090:9090 jamtur01/fortunes:v1.0ff109a7f62f13f5fc7b9fb701081ba5af8bfd706bffafaf324c700fd0ff31ffa
And then we can see the container running.
$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTSff109a7f62f1 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/fortunesFortune 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.