Daniel Amado

Middleware as code with Ballerina

Let’s assume that we need to facilitate the integration of several systems. What options do we have?

There are quite a few options for performing our integration, such as Enterprise Service Bus (ESB) or other frameworks like Spring and NodeJS.

Existing approaches

Enterprise Service Bus

Let’s start by looking at the ESB, one of the ways of integrating systems. ESB provides a range of services using a standard method of communication such as REST or SOAP.

It also helps monitor all the messages passing through it and ensures they are delivered to the right place by controlling the routing of each message.

You can use code to create the service logic, but service configuration (routing, users and passwords) doesn’t need to be hard coded, because the ESB provides ways to configure services outside the code. It also helps control deployments and versioning of services.

You need to take into account that this approach has a single point of failure and requires high configuration and maintenance.

Some approaches appear to have ESBs in containers, in order to be more ‘cloud-native’, but they still require a lot of configuration, not being very agile.

Spring and NodeJS frameworks

To achieve a more agile approach, we can use frameworks such as NodeJS or Spring to create our integration, however, to work with communication (endpoints, messages and payloads data) they require libraries and plugins and a lot of boilerplate code to deal with the payload type and make simple calls to services.

There is a new language dancing around to try to mitigate these problems between ESBs and frameworks like Spring and NodeJS. Let’s take a look at Ballerina.

Ballerina

Ballerina is a new language that is being created with the communication paradigm in mind. Ballerina allows concurrent work to be done and has transactional functions where the data is either all successfully altered or none of it is.

It has both a graphical syntax as well as a textual one. So, if you write the code with the textual syntax, you are helped by the graphical information generated afterwards. This graphical information can also be used as documentation, because it represents the flow of messages and intervenients in the service. You can check a comparison of the syntaxes in the following image (in the left the textual syntax and in the right the graphical syntax):

Since Ballerina is constructed with communication in mind, you can count on network data types to be fully supported. This way you don’t need to add libraries to manipulate json or xml.

Ballerina is built upon integration patterns, providing a QoS where the communication is resilient, transactional, secure and observable.

To get a better idea of how Ballerina works you can check the following page https://ballerina.io/philosophy/

Service and proxy example

If you want to try the following example in your machine, please follow the guide on how to install Ballerina on your computer under the next link: https://ballerina.io/learn/getting-started/#download-the-ballerina-distribution .

In this example we will check how to create two services. One will work as a resource provider, and the other as a proxy that will convert the resources from xml to json.

Let’s start with the first service and call it service1.

Service 1

To create the service, first we create a file named service1.bal and write the following code:

import ballerina/http;
import ballerina/log;
service service1 on new http:Listener(9090) {
    resource function getCars(http:Caller caller, http:Request req) {
        var page = xml `<response>
        <cars>
            <car>
                <model>Leaf</model>
                <plate-number>AB-12-CD</plate-number>
                <plate-date>2019-01-01</plate-date>
                <serial-number>AS6F4GR8154E5G841DF548R4G1WW</serial-number>
            </car>
            <car>
                <model>Yaris</model>
                <plate-number>AB-13-CD</plate-number>
                <plate-date>2019-04-03</plate-date>
                <serial-number>ESD5GFN2RG5H451SEWFDBGR3544D</serial-number>
            </car>
        </cars>
        </response>`;
        http:Response res = new;
        res.setPayload(page);
        var result = caller->respond(res);
        if (result is error) {
            log:printError("Error sending response", err = result);
        }
    }
}

In this service we have the function getCars with our logic: first we start to create the response as an xml and set it in the response payload. Evetually we respond to the caller, checking if there is an error responding.

To run the service we use the following command:

ballerina run service1.bal

 

This will create the service in port 9090 with the function getCars. We can check the response of the function in the address http://localhost:9090/service1/getCars in the browser or using curl.

Proxy

Now let’s create the proxy!

First we create another file with the name proxy.bal and then we write the following code:

import ballerina/http;
import ballerina/io;
import ballerina/log;
http:Client clientEndpoint = new("http://localhost:9090/service1");
service proxy on new http:Listener(9091) {
    resource function getCars(http:Caller caller, http:Request req) {
        //Get the xml response from getCars
        var responseGetCars = clientEndpoint->get("/getCars");
        
        if (responseGetCars is http:Response) {
            var msg = responseGetCars.getXmlPayload();
            if (msg is xml) {
                var responseJson = msg.toJSON({});
                //Create the response for this service
                http:Response res = new;
                res.setPayload(untaint responseJson);
                
                var result = caller->respond(res);
                if (result is error) {
                    log:printError("Error sending response", err = result);
                }
            else {
                io:println("Invalid payload received:" , msg.reason());
            }
        else {
            io:println("Error when calling the backend: ", responseGetCars.reason());
        }
    }
}

In the code you can see that we’ve created a service named proxy on port 9091 using a function called getCars. We have an endpoint for service1 (no plugins or libraries ), and in the function getCars, the first thing we do is call the function getCars from service1. Afterwards, we check whether the payload from the response of service1 is an xml; and if it is, we convert it to json using just one function! In the end we respond to the caller usng the json from the conversion.

To test the proxy we can use curl or to have a visual of the json, use your browser and the following address: http://localhost:9091/proxy/getCars

You can compare the output from service1 and the proxy and check that the information is the same in both.

Conclusion

In conclusion we can see that Ballerina is shaping quite well to be a promising language the integration of services, with an agile approach and less boilerplate code required for handling communication between services.

But do proceed with caution, because it hasn’t yet achieved a stable release, so syntax and semantics are still subject to change. The first stable release is expected at the end of this year.

Daniel AmadoMiddleware as code with Ballerina
read more