Ballerina Connectors for Seamless Integration

Maheeka Jayasuriya
10 min readNov 12, 2017

--

We just finished the WSO2 EUCon 2017 at London with huge success. For the first time, we had a dedicated track for Ballerina, discussing various aspects of Ballerina language and how it will change the world of integration as we know it.

I did a presentation at the conference on “Ballerina Connectors for Seamless Integration” and this article is based on the content that was discussed.

Following are the slides from the presentation.

Why Connectors?

We are now in the dynamic era of integrations where integration is extended to connecting cloud applications, internet of things, mobile applications, so on and so forth. Connectors are the medium of integration among these various dynamics, enabling solving integration problems fast and conveniently. With connectors comes the adaptiveness. Adaptiveness is what makes integration seamless.

Complexities of a today’s systems cannot be avoided as they cater a wide and a variety of audience of various identities and technologies. Connectors provide an abstraction to complicated systems by exposing a facade that is easy to be interacted upon.

When writing a Ballerina program to interact with various systems, there are a few concepts that you must first be familiar with. First is the conceptual and defined entities that are connectors and actions. Next is their applications in implementations in the form of endpoints, connections and action invocations.

Concepts

Entities: Connectors and Actions

Connector: “A connector represents a participant in the integration and is used to interact with an external system or a service that’s defined in Ballerina.”

Action: “An action is an operation you can execute against a connector. It represents a single interaction with a participant of the integration.”

Applications : Endpoints, Connections and Action Invocations

Endpoint:An endpoint is a logical entity that represents an external system or a service that is defined by connectors

Connection:A connection is the physical configurations that are required to connect to an external system or a service that is defined by connectors

Action Invocation: An invocation represents the interaction from a worker (default or other) to an endpoint via actions

Ballerina Composer : Endpoints and action invocations in Ballerina Composer
Ballerina Composer : Connection declaration in endpoints

Above is the visual representation of endpoints, connections and action invocations in Ballerina composer. Since a connector is a participant and endpoint is the logical entity that represents it, it is shown as a lifeline in the sequence diagram like view. The action invocations to the endpoint is represented as inward and outward arrows from worker to the endpoint.

Connector Types — Server and Client Connectors

Server connectors

Server connectors are inbound connectors that act as servers in Ballerina.

E.g. : HTTP(s), WebSocket, JMS, File, IO, etc

Client Connectors

Client connectors are outbound connectors that interact with external systems.

E.g. : HTTP(s), WebSocket, Salesforce, Twitter, JMS, File, SQL

API Connectors: Client connectors that connect to cloud APIs and services. E.g. : Twitter, Salesforce

Sample Ballerina Implementations

HTTP Server Connector

Using the HTTP server connector in a Ballerina program is in the form of services. You can define the type of server connector in the protocol and implement the resources based on the protocol selected. This can be different for each server connector.

Consider the following echo service which will simply accept a request payload and respond with the same payload to the client.

import ballerina.net.http;@http:configuration {
basePath:"/echo"
}
service<http> echo {
@http:resourceConfig {
methods:["POST"],
path:"/message"
}
resource echoMessage (http:Request req, http:Response resp) {
string payload = req.getStringPayload();
resp.setStringPayload(payload);
resp.send();
}
}
Slide 9 : Defining a service with HTTP server connector

Invoking the service :

>> curl http://localhost:9090/echo/message -d ‘Hello Ballerina!’
>> Hello Ballerina!

In HTTP server connector, a service will have a base path, and a resource will have the path and the HTTP method defined within @http:configuration and @http:resourceConfig annotations, respectively. These configuration annotations are defined in the HTTP server connector. Apart from the annotations that are defined above, there are many other annotations that can support complex service definitions with path parameters, query parameters, consumer and producer content types, etc..

HTTP Client Connector

Below is a sample Ballerina code for invoking an HTTP endpoint created with HTTP client connector.

function getOrders(http:Request req) {
endpoint<http:HttpClient> productsService {
create http:HttpClient("http://localhost:9090", {});
}
http:HttpConnectorError err;
http:Response clientResponse = {};
clientResponse, err = productsService.get("/orderservice/orders", req);
}
Slide 12: Declaring HTTP endpoint and invoking GET action

Let’s break down step by step on the above sample. First step is the endpoint declaration with the http:HttpClient connector type and a connection.

endpoint<http:HttpClient> productsService {
create http:HttpClient("http://localhost:9090", {});
}

Then GET action is invoked on this endpoint to get the response.

clientResponse, err = productsService.get("/orderservice/orders", req);

Let’s explore more details on using client connectors later in the article.

Writing Client Connectors

A connector is made up of actions and connections. Let’s see what a simple connector definition looks like for a Twitter client connector.

connector TwitterClient(string consumerKey , string consumerSecret, 
string accessToken, string accessTokenSecret) {
action tweet(string status) (http:Response) {
}
}

Above is a skeleton of the Twitter connector definition. We first declare a connector with parameters that are required to create a connection. If we take Twitter, the parameters to create a connection is the authentication details and hence they become parameters to the TwitterClient connector.

Actions are defined within a connector and will have parameters and return parameters similar to a function. The required parameters to invoke the action are parameters and the returns are the responses to the action invocation.

Following is a complete Twitter connector implementation done using Ballerina.

import ballerina.net.http;
import ballerina.net.uri;
import ballerina.util;
@Description{ value : "Twitter client connector."}
@Param{ value : "consumerKey: The consumer key of the Twitter account."}
@Param{ value : "consumerSecret: The consumer secret of the Twitter account."}
@Param{ value : "accessToken: The access token of the Twitter account."}
@Param{ value : "accessTokenSecret: The access token secret of the Twitter account."}
connector TwitterClient (string consumerKey , string consumerSecret,
string accessToken, string accessTokenSecret){
endpoint<http:HttpClient> twitterEp {
create http:HttpClient("https://api.twitter.com", {endpointTimeout:10000});
}
@Description{ value : "Update the authenticated user's current status."}
@Param{ value : "status: The text of status update"}
@Return{ value : "Response object."}
action tweet(string status) (http:Response) {
http:Request request = {};
map parameters = {};
string urlParams;
string tweetPath = "/1.1/statuses/update.json";
status = uri:encode(status);
parameters["status"] = status;
urlParams= "status=" + status;
tweetPath = tweetPath + "?" + urlParams;

constructRequestHeaders(request, "POST", tweetPath, consumerKey, consumerSecret, accessToken,
accessTokenSecret, parameters);

http:HttpConnectorError err;
http:Response response;
response, err = twitterEp.post(tweetPath,request);
return response;
}
}

If you look through the above code, there is an http endpoint that is created for the Twitter REST API at the connector definition level. This endpoint will be invoked during action invocations performed on the Twitter connector. Within the tweet action, there are various steps involved for adding authentication headers, constructing urls with parameters and finally invoking a post on the twitter HTTP endpoint and returning the response.

Invoking this Twitter connector is extremely simple and similar to HTTP client connector invocation.

function testTweet (string consumerKey, string consumerSecret, 
string accessToken, string accessTokenSecret) (http:Response) {
endpoint <TwitterClient> twitterEndpoint {
create TwitterClient(consumerKey, consumerSecret,accessToken, accessTokenSecret);
}
return twitterEndpoint.tweet("Hello Ballerina!!");
}

If you compare these two code blocks, it is clear the level of abstraction and simplicity a connector provides. When you use connectors, you do not need to know about underlying processes of setting headers or constructing requests. You simply need to provide the bare minimum required details to serve your purpose.

Using Client Connectors : Endpoints, Connections and Decorators

Let’s go back to the above sample of using the HTTP client connector to discuss few more concepts and usage patterns around endpoints and connections.

Declaring an Endpoint

endpoint<http:HttpClient> productsService {}

When declaring an endpoint, it must be constrained by a client connector type as <http:HttpClient>. This is known as the base connector for a particular endpoint. In this case, this endpoint is of http:HTTPClient type. What this means is that this endpoint can only hold a connection of http:HTTPClient connector.

Creating a Connection

create http:HttpClient("http://localhost:9090", {});

In order to create a connection for a connector, you need to provide the configuration parameters that are required to physically connect to the external system that is represented by the connector. These configurations can be URL, hostname, username, password, etc.

Binding Connection to Endpoint

One way of binding a connection to an endpoint is during the declaration as is in the above example.

endpoint<http:HttpClient> productsService {
create http:HttpClient("http://localhost:9090", {});
}

Other way is a bind statement that is used to bind a connection to an endpoint.

Suppose the endpoint is declared without a connection as below or as above with a connection.

endpoint<http:HttpClient> productsService {}

In either of above declaration of an endpoint, we can choose to bind a connection to the endpoint. The first step is to create a connection and assign it to a variable.

http:HttpClient httpConnection = create http:HttpClient("http://localhost:9090", {});

Now you can bind the connection as below.

bind httpConnection with productsService

If an endpoint has a connection already bound to it, above statement will overwrite the existing connection. The important thing to remember is that you cannot use an endpoint without a connection. The bind statement is highly useful when you want to change the connections dynamically in the program logic. Also since Ballerina programs only allow endpoint declarations as topmost statements of a component, this will allow you to create connections later on in the program and use them as needed.

Another way of declaring endpoints is to bind it with a connection variable during the declaration.

function setOrder(http:HTTPClient httpConnection, Order order) {
endpoint<http:HttpClient> productsService {
httpConnection;
}
}

Note that the connection is passed as a parameter to the function. This is because the endpoints have to be always declared first in a constructor in Ballerina.

Decorating Connectors

Consider the following example for declaring an endpoint.

endpoint <http:HttpClient> basicAuthEp{
create BasicAuthClient(
create http:HttpClient("http://localhost:9090", {}), username, password);
}

As we discussed earlier, the endpoint basicAuthEp has been declared with http:HttpClient connector as the constraining base type. However, the connection that is bounded to the endpoint is a BasicAuthClient. What we see here is the concept of using decorated connectors as endpoint connections.

In order to use a connection created from a different connector other than the base connector, the decorating connector has to conform to the base connector. In order to conform or to be compatible, the decorating connector has to have the exact same actions with the same signature — name, parameters and returns; as the base connector.

Here the BasicAuthClient conforms to HTTPClient connector and hence can be used as a connection for basicAuthEp. Following is sample code for BasicAuthClient.

connector BasicAuthClient(http:HttpClient con, string username, string password) {
endpoint <http:HttpClient > httpEp{
con;
}

action get (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.get(path, request);
return response, err;
}

action post (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.get(path, request);
return response, err;
}

action put (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.put(path, request);
return response, err;
}

action head (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.head(path, request);
return response, err;
}

action delete (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.delete(path, request);
return response, err;
}

action patch (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.patch(path, request);
return response, err;
}

action execute (string httpVerb, string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.execute(httpVerb,path, request);
return response, err;
}
action forward (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.forward(path, request);
}
}
function setBasicAuthHeader(http:Request request, string username, string password) {
string token = util:base64encode(username + ":" + password);
request.setHeader("Authorization", "Basic " + token);
}

Note that this has all the actions that are in http:HttpClient connector (https://ballerinalang.org/docs/api/0.95.0/ballerina.net.http.html#HttpClient). As with “Decorator Pattern”, in the new BasicAuthClient connector, there is a http:HttpClient endpoint that is passed as a connector parameter during connector initialization as httpEp.

create BasicAuthClient(
create http:HttpClient("http://localhost:9090", {}), username, password);

Let’s take a look at the get action of the new BasicAuthClient connector.

action get (string path, http:Request request) (http:Response, http:HttpConnectorError) {
http:Response response;
http:HttpConnectorError err;
setBasicAuthHeader(request, username, password);
response, err = httpEp.get(path, request);
return response, err;
}

The GET action of the BasicAuthClient will add the decorating logic for adding the basic-auth header with setBasicAuthHeader(request, username, password) method. Then after using the httpEp the real action invocation is performed.

When should you use connectors?

In summary, you should use connectors for your integrations as much as possible since it helps you write fast, convenient and reliable code. Ballerina has a bunch of built-in connectors. When it comes to network connectors like HTTP(s) or WebSocket, these are built-in to Ballerina and hence is the essence and core of Ballerina. But if we consider API connectors such as Twitter, Salesforce, etc, you might feel that you can write simple integration logic without using connectors. However, it is not the case. When a connector is provided for Ballerina, it is a reliable, thoroughly tested and evaluated to be reliable and fail safe. If you try to write your own connectors, there might be mountains of complexities and issues that you will have to figure out down the line. Therefore, it is a wise choice to use such readily available connectors for your integrations.

Ballerina provides a number of various connectors and a lot more are under development and will be available for you soon. You can take a look at the connectors that are available at https://ballerinalang.org/connectors/. You can also contribute to existing connectors or write your own connectors to be shared with the open source community with our GitHub organization https://github.com/ballerinalang.

Also refer :

https://wso2.com/library/conference/2017/11/wso2con-eu-2017-ballerina-connectors-for-seamless-integration/

--

--