info@yenlo.com
eng
Menu
WSO2 Tutorial 12 min

Exploring Ballerina: A first Dance

Thijs Volders
Thijs Volders
Strategic Technology Officer
wso2 ballerina language

Ballerina - flexible, powerful, beautiful.png During the WSO2 US Conference 2017 the new programming language for WSO2 products that use mediation and transformation was announced called Ballerina. Ballerinas are graceful, slim, in good physical shape and lightweight. These characteristics are directly tied to their profession. Dancing at that professional level requires a lot from a person and a body.

Real ballerina.png So, if you call your new programming language Ballerina, you raise the bar for performance. I think that WSO2 is aware of this and they will come up with a graceful, powerful yet lightweight product.

What is Ballerina?

It is an open-source project initiated by WSO2 to foster the development of the programming language called Ballerina and its supporting technologies like IDEs, Debuggers, Testing-Frameworks and so on. Ballerina is not a product but it will be used in the future as a component within products. Although WSO2 is the initiator and main sponsor of the development of Ballerina everybody is free to join and contribute. Right now it is a technology preview currently in version 0.8.3.

EIP

However when we look at the support for Enterprise Integration Patterns (EIP) we see that this version does not support all patterns. EIP has been an integral part of the WSO2 ESB and are widely used. As it looks now, the Enterprise Integrator 7.0.0 is the successor to version 6.0.0 and support for EIP is needed in order to migrate from 6.0.0 to 7.00.

This means that before we get to version 1.0.0 things might change and additional functionality will be added. There is no timeline at the moment but we speculate that it will be about a year before we will see a full blown Ballarina language..

Ballerina language is not considered to be a Domain Specific Language; it is a general purpose, modern, concurrent and strongly typed programming language optimized for integration. Although it is a general purpose language the designers of it state clearly the primary domain is integration and you should not be tempted to use it for developing the next high performance 3D renderer.  At this moment, Ballerina comes along with an interpreter, but in the future, is planned to compile the code to a binary form (like bytecode in Java). The language is inspired by a variety of languages like the also open source Go language developed by Google.

However, the Ballerina language is open source and its supporting components like the interpreter runtime, the graphical frontends are written in Java which has been the main language for WSO2 products. By that the current runtime can benefit of the entire Java ecosystem and the team is not forced to reinvent the wheel. Anyhow Ballerina is not limited to the Java ecosystem.

Ballerina provides an IDE (called composer) that runs in a web browser. The IDE allows textual, graphical and Swagger editing of Ballerina programs. Core in this case is stability of the IDE. The current graphical environment is, especially for novice users, somewhat unforgiving when switching between source and design, resulting in lost pieces of code. This can be minimalized by for instance saving regularly or not switching so much between two modes but nevertheless can be an area of improvement.

Figure 1 Text editor (source view).png Figure 1 Text editor (source view)

Figure 2 Graphical editor (design view).png Figure 2 Graphical editor (design view)

Figure 3 Swagger editor (Swagger view).png Figure 3 Swagger editor (Swagger view)

The IDE gives the possibility to run or debug both models of Ballerina programming:

  • application – an application containing main() function (so more like a regular program)
  • service – network invoked collections of entry points (more like a proxy or API in the current ESB).

There are also plugins available for popular IDEs like IntelliJ IDEA, Atom, vim, Sublime Text 3 and Visual Studio Code.

Figure 4 Visual Studio Code with Ballerina plugin.png Figure 4 Visual Studio Code with Ballerina plugin

Examples

HTTP Service

To test the new language, we have taken a service we have created in the ESB 5.0.0 and ported it to Ballerina. This is handy because it will give an idea what a migration path will look like because, as WSO2 has also mentioned, migration of current artifacts to Ballerina will be necessary.

The service connects to Yenlo Cars REST API and gets the data in XML or JMS format. The read data are sent to a JMS queue named ballerina.cars.http.

We will explain the functionality by adding comments # in red (keep in mind that Ballerina does not support comments in the source) in the actual program. This is not magic, we just refer to the documentation found online. In all, what we are doing is creating an http message with basic authorization (base64 encoded) and path with a URI template or URI context. We also do a little bit of error handling / exception catching.

#
# Import the packages needed to run the program, as you can see we will use xml, json, jms
# and so on
#
import ballerina.net.http;
import ballerina.net.jms;
import ballerina.lang.exceptions;
import ballerina.lang.messages;
import ballerina.lang.system;
import ballerina.lang.strings;
import ballerina.lang.jsons;
import ballerina.lang.xmls;
import ballerina.utils; # Create a service
@http:BasePath("/count")
service YenloCarsCountService {
  # Create a connector to a http service
  http:ClientConnector carsService = create
http:ClientConnector(
      "https://yenlo-cars.herokuapp.com/");          # Retrieve the count of the number of cars
  @http:GET
  @http:Path("/")
  resource count(message m) {
    message response = {};
    try {
      response = http:ClientConnector.get(carsService, "/count", updateHeader(m));
      # send result to ActiveMQ queue
      send(response);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }   # Retrieve the count the number of cars per mark
  @http:GET
  @http:Path("/marks")
  resource countMarks(message m) {
    message response = {};
    try {
      response = http:ClientConnector.get(carsService, "/count/marks", updateHeader(m));
      # send result to ActiveMQ queue
      send(response);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }   # Retrieve the count the number of cars for a mark
  @http:GET
  @http:Path("/marks/{mark}")
  resource countMark(message m, @http:PathParam("mark")string mark) {
    message response = {};
    try {      response = http:ClientConnector.get(carsService,
          "/count/mark/" + mark, updateHeader(m));
      # send result to ActiveMQ queue
      send(response);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }
} # Update the header for needed attributes Content-Type, Accept and Authorization
# Randomize the answer’s format xml or json
function updateHeader(message m)(message) {
  string type = "";
  if ((int)(system:nanoTime() % 2) == 0) {
    type = "application/json";
  }
  else {
    type = "application/xml";
  }
  messages:addHeader(m, "Content-Type", type);
  messages:addHeader(m, "Accept", type);
  messages:addHeader(m, "Authorization",
      "Basic " + utils:base64encode("yenlo-cars:yenlo-cars-demo"));
  return m;
} 

# Prepare and send the message to ActiveMQ queue
function send(message m)throws exception {
  map propertyMap = {};
  message msg = {};
  string value;
  try {
    if (strings:contains(messages:getHeader(m, "Content-Type"), "application/json")) {
      value = jsons:toString(messages:getJsonPayload(m));
    }
    else {
      value = xmls:toString(messages:getXmlPayload(m));
    }
    messages:setStringPayload(msg, value);
    jms:ClientConnector jmsAMQ = create jms:ClientConnector(        "org.apache.activemq.jndi.ActiveMQInitialContextFactory",
        "tcp://localhost:61616");
    jms:ClientConnector.send(jmsAMQ, "QueueConnectionFactory",
        "ballerina.cars.http", "queue", "TextMessage", msg, propertyMap);
  }
  catch (exception e) {
    throw e;
  }
}

# Run a service (from the project’s home directory)
$ $BALLERINA_HOME/bin/ballerina run service src/main/ballerina/com/yenlo/http/CountService.bal

Database Service

The service reads records from MySQL database and returns them in JSON format. All the returned data are sent to JMS queue ballerina.cars.sql.

#
# Import the packages needed to run the program, as you can see we will use xml, json, jms
# and so on
#
import ballerina.data.sql;
import ballerina.net.jms;
import ballerina.lang.datatables;
import ballerina.lang.exceptions;
import ballerina.lang.messages;
import ballerina.lang.jsons;
import ballerina.lang.system; # Create a service
@http:BasePath("/count")
service YenloCarsDBService {
  # Connection’s properties
  map connection = {
    "driverClassName":"com.mysql.jdbc.Driver",    "jdbcUrl":"jdbc:mysql://onnjomlc4vqc55fw.chr7pe7iynqr.eu-west-1.rds.amazonaws.com:3306/kfnda31gr0xld8tx",
    "username":"sb8ane8p28cycgwb",
    "password":"ft2rtycukmck6fol"
  };
  # Create a connector to a database
  sql:ClientConnector carsDB = create sql:ClientConnector(connection);          # Retrieve the count the number of cars in database
  @http:GET
  @http:Path("/")
  resource count(message m) {
    sql:Parameter[] params = [];
    message response = {};
    try {
      response = process(carsDB, "select count(1) as records from car", params);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }   # Retrieve the count the number of cars per mark
  @http:GET
  @http:Path("/marks")
  resource countMarks(message m) {
    sql:Parameter[] params = [];
    message response = {};
    try {
      response = process(carsDB,
          "select distinct(mark), count(1) as records from car group by mark", params);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }   # Retrieve the count the number of cars for a mark
  @http:GET
  @http:Path("/marks/{mark}")
  resource countMark(message m, @http:PathParam("mark")string mark) {
    sql:Parameter param = {sqlType:"varchar", value:mark, direction:0};
    sql:Parameter[] params = [param];
    message response = {};
    try {
      response = process(carsDB,
          "select mark, count(1) as records from car where mark = ?", params);
    }
    catch (exception e) {
      system:log(5, exceptions:getCategory(e) + " : " + exceptions:getMessage(e));
    }
    reply response;
  }
} # Execute SQL query, send result to ActiveMQ queue and return it
# Throws an exception in the case of any error
function process(sql:ClientConnector cc, string query, sql:Parameter[] params)(message)
    throws exception {
  # execute SQL query
  datatable dt = sql:ClientConnector.select(cc, query,
params);
  message msg = {};
  try {
    # get a result in JSON format
    json records = datatables:toJson(dt);
    messages:setJsonPayload(msg, records);
    # send result to ActiveMQ queue
    send(jsons:toString(records));
  }
  catch (exception e) {
    throw e;
  }
  # close database connection
  datatables:close(dt);
  return msg;
} # Prepare and send the message to ActiveMQ queue
function send(string text) throws exception {
  map propertyMap = {};
  message msg = {};
  try {
    messages:setStringPayload(msg, text);
    jms:ClientConnector jmsAMQ = create jms:ClientConnector(        "org.apache.activemq.jndi.ActiveMQInitialContextFactory",
        "tcp://localhost:61616");
    jms:ClientConnector.send(jmsAMQ,
"QueueConnectionFactory",
        "ballerina.cars.sql", "queue", "TextMessage",
        msg, propertyMap);
  }
  catch (exception e) {
    throw e;
  }     
}
# Run a service (from the project’s home directory)
$ $BALLERINA_HOME/bin/ballerina run service src/main/ballerina/com/yenlo/sql/CountService.bal

Apache ActiveMQ Service

The service reads all messages from the JMS queues named ballerina.cars.* and sends it to the topic ballerina.cars.

#
# Import the packages needed to run the program
#
import ballerina.net.jms; # JMS connection’s properties
@Source (
  protocol = "jms",
  destination = "ballerina.cars.*",
  connectionFactoryJNDIName = "QueueConnectionFactory",
  factoryInitial = "org.apache.activemq.jndi.ActiveMQInitialContextFactory",
  providerUrl = "tcp://localhost:61616",
  connectionFactoryType = "queue")
# Create a service
service ActiveMQQueueService {   # handle an event onMessage. Forward read message to ActiveMQ topic
  resource onMessage(message m) {
    map propertyMap = {};
    jms:ClientConnector jmsAMQ = create jms:ClientConnector(        "org.apache.activemq.jndi.ActiveMQInitialContextFactory",
        "tcp://localhost:61616");
    jms:ClientConnector.send(jmsAMQ, "TopicConnectionFactory",
        "ballerina.cars", "topic", jms:getMessageType(m), m, propertyMap);
  }
}
# Run a service (from the project’s home directory)
$ $BALLERINA_HOME/bin/ballerina run service src/main/ballerina/com/yenlo/jms/JMSService.bal

To run this Ballerina service its necessary to copy ActiveMQ supporting files from $ACTIVEMQ_HOME/lib (used Apache ActiveMQ v 5.14.3):

  • activemq-broker-5.14.3.jar
  • activemq-client-5.14.3.jar
  • geronimo-jms_1.1_spec-1.1.1.jar
  • geronimo-j2ee-management_1.1_spec-1.0.1.jar
  • hawtbuf-1.11.jar

to $BALLERINA_HOME/bre/lib.

To run the service YenloCarsDBService it’s necessary to copy a MySQL JDBC driver jar to $BALLERINA_HOME/bre/lib.

Performance

As always it’s expected of new product that it will have good performance and will not consume a lot of memory. It seems that WSO2 Ballerina meets these expectations. During our tests we didn’t observe huge memory consumption.

Figure 5 jconsole overview.png Figure 5 jconsole overview

Test

We tested access to a HTTP service directly on the local machine and via Ballerina’s service YenloCarsCountService to compare performance results. We used the gatling.io testing framework. The tests simulate 10 users which call 5 times the endpoints /count, /count/marks and /count/marks/HONDA.

ServiceLocalhost

A Gatling simulation to measure the access time to the Yenlo Cars HTTP service directly running on local machine.

package com.yenlo.compare;import io.gatling.core.Predef._
import io.gatling.http.Predef._ class ServiceLocalhost extends Simulation {
  val map = Map(
      "Authorization"->"Basic eWVubG8tY2Fyczp5ZW5sby1jYXJzLWRlbW8=");
  val json = map + ("Content-Type"->"application/json", "Accept"->"application/json");
  val xml = map + ("Content-Type"->"application/xml", "Accept"->"application/xml");    val count = http("count").get("/count");
  val marks = http("marks").get("/count/marks");
  val countHonda = http("count-honda").get("/count/mark/HONDA"));  val scnJSON = scenario("json").repeat(5) {
    exec(count.headers(json)).pause(5).
    exec(marks.headers(json)).pause(5).
    exec(countHonda.headers(json)).pause(5);
  }
    val scnXML = scenario("xml").repeat(5) {
    exec(count.headers(xml)).pause(5).
    exec(marks.headers(xml)).pause(5).
    exec(countHonda.headers(xml)).pause(5);
  }
   val httpConf = http.baseURL("http://tszielin:8080");
  setUp(
    scnJSON.inject(atOnceUsers(5)),
    scnXML.inject(atOnceUsers(5))
  ).protocols(httpConf);
}

BallerinaService

A Gatling simulation to measure the access times to the Yenlo Cars HTTP service running on local machine via a Ballerina service.

package com.yenlo.compare;import io.gatling.core.Predef._
import io.gatling.http.Predef._ class BallerinaService extends Simulation {
  val count = http("count").get("/count");  val marks = http("marks").get("/count/marks");
  val countHonda = http("count-honda").get("/count/marks/HONDA"));
    val scn = scenario("ballerina").repeat(5) {
    exec(count).pause(5).
    exec(marks).pause(5).
    exec(countHonda).pause(5);
  }

val httpConf = http.baseURL("http://localhost:9090");

  setUp(
    scn.inject(atOnceUsers(10))
  ).protocols(httpConf);
}

Results

The results are nearly the same. As presented on the Gatling overall simulation report the response times are nearly the same in the standard ranges. We see on chart that for 50 requests, we get the response in the time less then 0,8 second. The rest of requests have response in the time more than 1,2 seconds. The standard statistics such as min, max, average, standard deviation and percentiles globally and per request presented in table also contains nearly same values. In average the difference between request per second is 0,02.

Summarized the test, we didn’t observe that accessing the endpoint directly is significantly faster than via the Ballerina service. Please bear in mind that ServiceLocalhost only gets its data from the HTTP REST endpoint and the BallerinaService also sends its data to ActiveMQ.

Figure 6 ServiceLocalhost results .png Figure 6 ServiceLocalhost results

Figure 7 BallerinaService results .png Figure 7 BallerinaService results

Conclusion

We see that Ballerina already can connect to backend services like the Yenlo Cars API and that it produces results.

However, as stated in the beginning, it is a technology preview. That means that it is not finished yet and the community is working hard these days to push the language towards the 1.0.0 version.

To sum up our comments:

  • The Ballerina product still in development. Missing right now some implementations, like null keyword or comments in code.
  • The Ballerina language simplifies interaction with an external system or a service you’ve defined in Ballerina. Interaction with HTTP/HTTPS v1/v2, JMS, WebSocket and FTP/FTPS/SFTP is very simple using build-in connectors. Also access to databases very simple using a connector.
  • The language is simple, but for Java developers can be missing OOP functionality like inheritance (is-as) or some language syntaxes like ?: or for(…);
  • XML and JSON are first class citizens in Ballerina. Which is just great because both of them will be around for quite some time in the future.
  • The Ballerina language seems to be a nice tool to integrate services and platforms
  • We’re missing the capability to #comment the Ballerina Code.
  • Surprisingly one cannot execute a program with unused imports (clean code?)
Running JMSService.bal service.
JMSService.bal:2: unused import package '"ballerina.lang.system"'
Execution Ended.

The listed in article Ballerina examples are available on Bitbucket.

If you have any questions about this blogpost contact us via the comments section of this blog. For more View also our WSO2 Tutorials, webinars or white papers for more technical information. Need support? We do deliver WSO2 Product Support, WSO2 Development Support, WSO2 Operational Support and WSO2 Training Programs.

Thanks to Jochen, Rob and Thijs for their contribution to this blog.

eng
Close