Ballerina Packaging Architecture and Understanding the Client Connectors

Dushan Abeyruwan
6 min readApr 2, 2017

In my first blog [1], we focused on a generic concept of Ballerina programming language. If you read the blogs available under [2], you will probably get a good understanding of key features that Ballerina offers. Anyway, I thought of composing this blog and thought of covering following sections with a very simple but quite powerful sample.

Will try to cover;

  1. How we can work with packing architecture of Ballerina configs.
  2. Understanding connectors, because a connector allows you to interact ​mainly ​with third-party ​APIs. ​This​ enables​ you to connect and interact with APIs such as Twitter,​ ​Gmail, and Facebook​ easily. Thus its quite handy if we know the language contracts of Connectors.

Ok, lets start. Why do we need a packing architecture?;

Ballerina has a Go-like modularity approach for how it manages names and how code is organized into files. In summary, Ballerina entities (functions, services etc.) all have globally unique qualified names consisting of their package name and the given name. If a package is not specified then the symbol will be in the default (unnamed) package. Notice that the name of the file that the entity resides in plays no part in the name of the entity. Similar to Go, a package name is a collection of simple names concatenated with “.” and they are stored in the file system using a subdirectory structure with each splitting at the “.”.

With such a structure, when a Ballerina program has code in many packages, we need to define how such a program can be packaged for self-contained execution and how the runtime searches for and discovers any imported packages.

I will explain the importance of such approach with a proper sample. Lets take a look at the following sample.

ballerinaDemo.bal

package org.wso2.tutorial.demo;import ballerina.lang.system;
import ballerina.lang.messages;
import org.wso2.ballerina.connectors.facebook;
import org.wso2.ballerina.connectors.twitter;
function main (string[] args) {message m = {};
messages:setStringPayload(m, args[0]);
fork (m) {
worker tweetWorker (message m) {
string consumerKey = "";
string consumerSecret = "";
string accessToken = "";
string accessTokenSecret = "";
twitter:ClientConnector twitterConnector = create twitter:ClientConnector(consumerKey, consumerSecret, accessToken, accessTokenSecret);string textMessage = messages:getStringPayload(m);message tweetResponse = twitter:ClientConnector.tweet(twitterConnector, textMessage);reply tweetResponse;}worker fbPublishWorker (message m) {string fbToken = "";facebook:ClientConnector facebookConnector = create facebook:ClientConnector(fbToken);string fbUserId = "";string textMessage = messages:getStringPayload(m);message facebookResponse = facebook:ClientConnector.createPost(facebookConnector, fbUserId, textMessage, "null", "null");reply facebookResponse;
}
} join (all) (message[] publishResponses) {
system:println(messages:getJsonPayload(publishResponses[0]));
system:println(messages:getJsonPayload(publishResponses[1]));
} timeout (60000) (message[] publishResponses) {
system:println("error occurred");
json error = `{"error":{"code":"500", "reason":"timed out"}}`;
system:println(error);
message res = {};
messages:setJsonPayload(res, error);
message[] results;
results[0] = m;
}
}

Very simply, the above sample will execute the the main function and will publish on both Twitter and Facebook in a parallel execution (fork-join). When it comes to the industrial practices, developers always require to work with packing structures, where they have to prepare the modules and sometimes they have to collaborate external packages /APIs in order to implement the use case. If you look at the sample given above, in ballerinaDemo.bal, that particular piece of code utilize external packages (Facebook + Twitter), and , the given ballerina configuration packaged as ‘package org.wso2.tutorial.demo’. Please do refer [1] for advance information related to packaging ballerina configuration.

Creating Ballerina archives

First, you need to prepare the above configuration, with proper folder hierarchy. When a program is packaged using the ballerina build command, the resulting archive will contain not just the Ballerina files that contain the main function and/or services, but also all the Ballerina packages that are imported by all the code needed to execute the main function and/or services.

Please note there is two type of archives

  • A Ballerina executable archive containing a main() function is named with the extension “.bmz”. Use the following command to build an executable archive:

ballerina build main <main-package-name> [-o filename]

  • A Ballerina service archive containing one or more services is named with the extension “.bsz”. Use the following command to build a service archive:

ballerina build service <pkg1> [<pkg2> <pkg3> …] [-o filename]

Now let’s focus on how to create executable archive. For that, you need to run the ballerina build command, that will generates you ‘*.bmz’ archive. You will notice that ballerinaDemo.bal having main() function

├── org
│ └── wso2
│ └── tutorial
│ └── demo
│ └── ballerinaDemo.bal

Once you create the folder structure as given above, then you should run following command to create ‘bmz’ archive.

myworkspace-mac:bin dushan$ ./ballerina build main org/wso2/tutorial/demo  -o myproject

You will find that archive called ‘myproject.bmz’ at the root directory. If curious ,you can unzip myproject.bmz. The packing structure should be similar to whats given below.

myproject dushan$ tree
.
├── BAL_INF
│ └── ballerina.conf
├── ballerina
│ ├── doc
│ │ └── annotation.bal
│ └── net
│ └── http
│ └── annotation.bal
└── org
└── wso2
├── ballerina
│ └── connectors
│ ├── facebook
│ │ └── ClientConnector.bal
│ ├── oauth2
│ │ └── ClientConnector.bal
│ └── twitter
│ └── ClientConnector.bal
└── tutorial
└── demo
└── ballerinaDemo.bal

You can use following command to execute the archive you have prepared. Since I have used function, I used a ‘main’ function then passed “hello hi” as argument to the runtime where that will be used by Twitter and Facebook to publish.

./ballerina run main myproject.bmz  "hello hi"

Useful commands

Build and execute the main function without package reference

  • ballerina build main tasks.bal tasks.bmz
  • ballerina run main tasks.bmz

Build and execute main function resides under package org/test/calc

  • ballerina build main org/test/calc -o my-calc
  • ballerina run main my-calc.bmz

Build and execute service without package reference

  • ballerina build service foo.bal -o my-service
  • ballerina run service my-service.bsz

Build and execute service function resides under package org/test/calc

  • ballerina run service org/test/calc
  • ballerina build service org/test/calc my-service
  • ballerina run service my-service.bsz

Understanding the connectors

Connectors basically having two faces, one which is known as the Client connectors while other one is Server Connectors. A connector represents a participant in the integration and is used to interact with an external system or a service you’ve defined in Ballerina. Ballerina includes a set of connectors under org.ballerinalang.connectors.* that allow you to connect to Twitter, Facebook, and more (as client connectors). If you have been working with WSO2 ESB, they have almost 140+ odd connectors written in a way that helps you to interact with external systems without rewriting the same code (I was there as a part of initial design of WSO2 ESB connectors) [3]. So, in ballerina, the same has to be represented (nothing out of the box), but ballerina is adding more clarity in terms of connector representation.

A connector language definition is defined as follows:

[ConnectorAnnotations]
connector ConnectorName ([ConnectorParamAnnotations]TypeName VariableName[(, TypeName VariableName)*]) {
ConnectorDeclaration;*
VariableDeclaration;*
ActionDefinition;+
}

Facebook Connector language

package org.wso2.ballerina.connectors.facebook;import ballerina.doc;
import ballerina.lang.strings;
import ballerina.net.uri;
import org.wso2.ballerina.connectors.oauth2;@doc:Description{ value : "Facebook client connector."}
@doc:Param{ value : "accessToken: Value of the valid access_token."}
connector ClientConnector (string accessToken) {
string baseURL = "https://graph.facebook.com";oauth2:ClientConnector facebookEP = create oauth2:ClientConnector(baseURL, accessToken, "null", "null",
"null", "null");
@doc:Description{ value : "Create a post for user, page, event or group. "}
@doc:Param{ value : "f: The facebook Connector instance"}
@doc:Param{ value : "id: The identifier."}
@doc:Param{ value : "msg: The main body of the post."}
@doc:Param{ value : "link: The URL of a link to attach to the post."}
@doc:Param{ value : "place: Page ID of a location associated with this post."}
@doc:Return{ value : "Response object."}
action createPost(ClientConnector f, string id, string msg, string link, string place) (message) {
string uriParams;
message request = {};
string facebookPath = "/v2.8/" + id + "/feed";
if(msg != "null"){
uriParams = uriParams + "&message=" + uri:encode(msg);
}
if(link != "null"){
uriParams = uriParams + "&link=" + uri:encode(link);
}
if(place != "null"){
uriParams = uriParams + "&place=" + uri:encode(place);
}
facebookPath = facebookPath + "?" + strings:subString(uriParams, 1, strings:length(uriParams));

message response = oauth2:ClientConnector.post(facebookEP, facebookPath, request);
return response;
}

Please have a look at [4], currently there are few connectors already developed but at draft stage, but you can get proper understanding on how easily you can write a connector. Once you write a connector, allyou need to do is to import the connector reference with above reference (i.e packing architecture information)

  1. Ballerina — Let’s dance !!!!!
  2. https://medium.com/ballerinalang
  3. https://store.wso2.com/store/pages/top-assets
  4. https://github.com/ballerinalang/connectors

--

--