Skip to main content

First impressions of the new Cloud Native programming language Ballerina

Nowadays everything is Cloud Native; everybody talks about CN tools, frameworks, solutions, and so on. On the other hand, those tools totally changed the way we design, develop, test and release modern applications. I think the number of issues that we solved with the new concepts is equal to the number of new challenges, so in short,     we simply shoveled problems from one hole to the other. Many new tools appeared on the market to make developers' life easier by
  • integrating software with the underlying infrastructure
  • watching file changes and building containers automatically
  • generating resource descriptors on the fly
  • allowing debugging in a running container
  • etc.
Next to the new tools, new programming languages such as Metaparticle, Pulumi or Ballerina have been born. The last one had my attention because others are extensions on top of some existing languages, while Ballerina is a brand new programming language, designed from scratch.
My first impression of it was this language is a creature of Dr Frankenstein because it is a mix of 4+ existing languages with a few unique and interesting features. But first, let's talk about what CN programming language means?

First of all, there is no clear definition however here is my personal opinion. Beginning with the most important quality, it has handy support to create and publish (micro)services easily. It must contain REST and RPC connectors with data representation/conversion to communicate with other services language-natively. Circuit breaker, client-side load balancer, fail-over and retry logic are also very convenient features. And finally, it has to support containers and orchestration platforms like Kubernetes. The same application has to run locally or in the public cloud.

The language itself is a compiled, transactional, statically, and strongly typed programming language with textual and graphical syntaxes. The compiler is written in Java. It generates an intermediate byte-code, which looks like Java but it isn't compatible with JVM. The byte-code runs on Ballerina Virtual Machine. The first version was in Java with GC, but later on, they re-implemented the VM and switched to a register-based technology (for more information please visit this page). The language has textual representation a.k.a. source code, and an editable flow diagram view (image from dzone.com).

If you are familiar with Java, Groovy, Go and Python you will be familiar with Ballerina. Similarities are:
  • Java
    • Object
    • Generic
    • Annotation
    • JUnit
    • JDBC
    • JMS
  • Groovy
    • Fork join and async library
    • Elvis operator
    • Nil lifting
    • String template literal
  • Go
    • Struct type, called record
    • Functions are first-class citizens
    • Function declaration
    • Dynamic interface implementation
    • Panic and error handling
    • Multi return value
    • Ignore variable "_"
    • Channels
  • Python
    • self.
    • __init()
I created a small application to demonstrate the language itself. My goal was to fill the project with as many features as possible, so please forgive the complexity of the examples. The application in nutshell: it exchanges gold to different currencies (ratios are hardcoded, so use at your own risk :D). So we have 3 grams of gold and it calculates the best price for example in USD. It has a CLI interface and uses 3 services to calculate the final price. Two of them serve the exchange ratio and the third one calculates the price.

First I would like to introduce the simplest exchange service, which contains only the basic language features.

  • Line 3: Our service will listen on port 9091.
  • Line 5: It has an endpoint called "getRate".
  • Line 6: It generates the response message with a type json. Built in types are int, float, decimal, byte, string, boolean, anydata, any, nil, json, xml, array, map, tuples.
    • Line 7-8: New http:Response initialization with JSON payload.
    • Line 9:- Sends the response to the caller, "_" means the application ignores the return value.
    This was obviously easy. Let's implement the response generator.

    • Line 1: @untained is a special annotation and is required because the compiler checks major security vulnerabilities including SQL injection, path manipulation, file manipulation, unauthorized file access, and unvalidated redirects (open redirects). @untained annotation marks return content as manually secured.
    • Line 1: Input parameter "string|error payload" looks weird, it's called union type, and means the type of payload is either string or error. Union type is the most questionable language behavior. It looks like bad practice at first glance, but on the other hand, Ballerina has many features which make them easier to use (match, type guard) or at least make them make sense.
    • Line 2: Type check, a type-guard check is a typing test construct that allows selective code execution based on the type of the expression that is being tested. That means in line 3, the payload is a string but an error in line 4.
    • Line 3: Json is a built-in type, so it's really easy to declare and use it.
    • Line 4: Power of string template literals.
    • Line 8: "match" is a cleverer switch, with support of simple, tuple, and record types (I will explain later).
    The first service is ready to use.

    # ballerina run exchange-simle.bal
    Initiating service(s) in 'exchange-simple.bal'
    [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9091
    (press Ctrl+c after curl to exit)
    
    # curl -d "EUR" http://localhost:9091/exchangeSimple/getRate
    {"currency":"EUR", "rate":35.5}
    


    As I mentioned earlier, the CLI tool talks with two services to figure out the best price for exchange. The previous version was the simplest one. In the next section I want to present an enterprise grade version of the same service.

    • Line 1: Define a custom error type.
    • Line 3: Our first Ballerina object. The concept isn't powerful as in Java. There are only two types of them, abstract and regular (non-abstract) objects. An abstract object can only contain member declarations and/or method signatures - method body isn't allowed. An instance automatically implements an abstract class if members and methods match it - like Go interfaces.
    • Line 4: Member definition: the visibility is limited to public or private and modifiers (transient, final, static) are not allowed.
    • Line 6: Constructor of the object.
    • Line 12: Built-in collections support immutability by freezing.
    • Line 17-24: Iterable collections have high order functions such as map, filter, average, sum, max, min, etc. The only problem with them is, that they don't support closures as a function in the current version (0.990.0) so it's impossible to use the outside context during execution. The code above compiles but throws a runtime exception.
    • Line 18: Split tuple into separated variables. The number of elements in the tuple isn't limited to two, so the well-known left() and right() don't exist in Ballerina. The only way to get access to the content of a tuple is to split it into new variables.
    • Line 28: Create a custom error.
    Next part of the application is the second service itself.
    • Line 2: Member declaration. Services are special objects in the background. Constructors and members are allowed, but functions not?? (i hope they will fix it as soon as possible).
    Last part of the service is the response generation.

    • Line 1: Records are Data Transfer Objects.
    • Line 4: Optional field definition. By default, all fields are mandatory in a record. Be careful with optional fields, because they throw KeyNotFound error in runtime!
    • Line 10: Member function declaration without a body?? GOTO 13
    • Line 13: Go style function attach to on object.
    • Line 14: "check" is a developer friendly error handling mechanism. In this case the return value of getTextPayload() is a union type string|error. The function return value is also a union type json|error. Because our function returns an error, check can replace manual error handling and returns the error of the line execution immediately as function return value.
    • Line 17: Ballerina has built-in converters for json, xml, map, and record, so it's very easy to transform from one type to the other.
    The Enterprise version of the exchange service is done. It's now time to create the calculator service (only multiply implemented)

    • Line 2: Custom type with a limited number of values.
    • Line 9: There are two types of records, closed and open. By default, all records are open, which means any number of additional fields can be append during initialization. "!..." sign closes the record, so only the listed fields are allowed.
    • Line 13: Nullable value definition. Null pointer error is one of the most common errors in runtime. In Ballerina, null assignment is accepted only for nullable values. Nullable types are separated types, so float and float? are different types!
    • Line 33: JSON to record conversion.
    • Line 34: Record to record conversion.
    The final part of the application is the CLI client. It connects to the exchange services parallel, finds the best price and than calls the calculator service to multiply the amount of gold with the given ratio.

    • Line 4: Global variable with a simple HTTP endpoint configuration.
    • Line 5: HTTP endpoint configuration with retry and timeout. The configuration has built-in circuit breaker, client-side load balancer, and failover between multiple services.
    • Line 19: "untaint" calls for Ballerina's built-in sanitizer.
    • Line 19: Typecasting from int to float.
    • Line 28: Error or nil lifting. Ballerina breaks line execution if the right side of "!" is an error or null and lifts the result.
    • Line 31: Stop function execution and throw a panic.
    • Line 35-43: Use fork-join to call services parallel.
    • Line 37: Catch panic with "trap" and convert it to a regular error.
    At that point the hard job is done. All of the services are ready and the command line interface glues the functionality together. We can run the whole application on our local machine, however we don't want to. We want to run our microservices on Kubernetes. Of course a CN programming language supports Kubernetes, so there is nothing else to do except annotate our services and Ballerina will generate all the Kubernetes resource descriptors, Helm charts and will build Docker containers in the configured registry.


    # minikube start
    # minikube addons enable ingress
    # eval $(minikube docker-env)
    # ballerina build
    Compiling source
        exchange-simle.bal
        exchange-enterprise.bal
        calculator.bal
        convert.bal
    Generating executables
        ./target/exchange-simle.balx
     @kubernetes:Service     - complete 1/1
     @kubernetes:Deployment     - complete 1/1
     @kubernetes:Docker     - complete 3/3
     @kubernetes:Helm     - complete 1/1
    
     Run the following command to deploy the Kubernetes artifacts:
     kubectl apply -f /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/exchange-simle
    
     Run the following command to install the application using Helm:
     helm install --name exchange-simle-deployment /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/exchange-simle/exchange-simle-deployment
    
        ./target/exchange-enterprise.balx
     @kubernetes:Service     - complete 1/1
     @kubernetes:Deployment     - complete 1/1
     @kubernetes:Docker     - complete 3/3
     @kubernetes:Helm     - complete 1/1
    
     Run the following command to deploy the Kubernetes artifacts:
     kubectl apply -f /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/exchange-enterprise
    
     Run the following command to install the application using Helm:
     helm install --name exchange-enterprise-deployment /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/exchange-enterprise/exchange-enterprise-deployment
    
        ./target/calculator.balx
     @kubernetes:Service     - complete 1/1
     @kubernetes:Deployment     - complete 1/1
     @kubernetes:Docker     - complete 3/3
     @kubernetes:Helm     - complete 1/1
    
     Run the following command to deploy the Kubernetes artifacts:
     kubectl apply -f /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/calculator
    
     Run the following command to install the application using Helm:
     helm install --name calculator-deployment /Users/mhmxs/GitHub/ballerina-gold-exchanger/target/kubernetes/calculator/calculator-deployment
    
        ./target/convert.balx
    # make kube-apply
    # make expose
    # ballerina run convert.bal 3 USD
    Handling connection for 9091 (call simple exchange service)
    Handling connection for 9092 (call enterprise exchange service)
    Handling connection for 9092 (the service doesn't work as I mentioned)
    Handling connection for 9092 (CLI retries a few times)
    Handling connection for 9090 (call calculator service)
    106.5
    # make cleanup
    


    I hope this introduction to the Ballerina language was interesting for all. The source code of the application is available on GitHub. Ballerina by Example is a beautiful collection to learn the language, so don't forget to visit it. For development I used Visual Studio Code plugin which worked well including syntax highlighting, auto-completing, jumping to the declaration, finding all references, and debugging (rename was broken). Intellij IDEA plugin is also available in the marketplace. There is one important thing to mention about IDE plugins. They require Java 8, so be sure you set the right version of Java.

    There are some other nice features that make the language more powerful. Here are just a few of them:
    • Swagger generator
    • Distributed transaction
    • XA transaction
    • Channel
    • Reactive streams
    • 3rd party plugin library
    In my view, the language is not production-ready yet. I faced and reported a few issues during the dating, but it has great potential to be the first united Cloud Native language on the globe.

    Popular posts from this blog

    Advanced testing of Golang applications

    Golang has a nice built-in framework for testing production code and you can find many articles on how to use it. In this blog post, I don't want to talk too much about the basics , table-driven testing ,  how to generate code coverage  or detect race conditions . I would like to share my personal experiences with a real-world scenario. Go is a relatively young and modern programming language on one side, and it is an old fashion procedural language on the other. You have to keep in mind that fact when you are writing production code from the beginning, otherwise, your program should become an untestable mess so easily. In a procedural way, your program is executed line by line and functions call other functions without any control of the dependencies. Hard to unit test, because you are testing underlying functions too, which are side effects from the perspective of testing.  It looks like everything is static if you are coming from object-oriented world. There are...

    Kubernetes and Calico development environment as easy as a flick

    I became an active member of the Calico community so I had to build my own development environment from zero. It wasn't trivial for many reasons but mainly because I have MacOS on my machine and not all of the features of Calico are available on my main operating system. The setup also makes some sense on Linux hosts, because if the node controller runs locally it might make changes to the system, which always has some risk in the playing cards. The other big challenge was that I wanted to start any version of Kubernetes with the ability to do changes in it next to Calico. Exactly I had to prepare two tightly coupled environments. My idea was to create a virtual machine with Linux on it, configure development environments for both projects in the VM and use VSCode 's nice remote development feature for code editing. In this way projects are hosted on the target operating system, I don't risk my system, I don't have to deal with poor file system sync between host a...

    Autoscaling Calico Route Reflector topology in Kubernetes

    Kubernetes is a great tool to organize your workloads on a low or high scale. It has many nice features in different areas, but it is totally out-sourcing the complexity of the network. Network is one of the key layers of a success story and happily there are many available solutions on the market. Calico is one of them, and it is I think the most used network provider, including big players in public cloud space and has a great community who works day by day to make Calico better. Installing Kubernetes and Calico nowadays is easy as a flick if you are happy with the default configurations. Otherwise, life became tricky very easily, there are so many options, configurations, topologies, automation, etc. Surprise or not, networking is one of the hard parts in high scale, and requires thorough design from the beginning. By default Calico uses IPIP encapsulation and full mesh BGP to share routing information within the cluster. This means every single node in the cluster is connected w...