Key Takeaways
- Ballerina is a new programming language and platform whose objective is to make it easy to create resilient services that integrate and orchestrate across distributed endpoints.
- Ballerina .970 has been released, promising language stability towards its 1.0 progression readied by the end of 2018.
- Ballerina is now hosted here and includes package management, dependency management, versioning, and a globally hosted registry for connectors and annotations at Ballerina Central.
- Ballerina’s design principles focus on baking integration concepts into a language, including a network-aware type system, sequence diagrammatic syntax, concurrency workers, being “DevOps ready”, and environment awareness.
Ballerina is an approach to make microservices programming simpler by making it easy to integrate APIs. Ballerina was started 3 years ago by architects from WSO2 as a response to challenges they experienced in building integrations with EAI, ESB, and workflow products.
About Ballerina
It’s unusual to develop a new programming language and runtime stack. Ballerina was born out of frustrations that the project leaders had in working with integration projects, which were increasingly disrupting the developer’s flow around edit, build, test, and repeat.
Configuration-based integration products, such as Apache Camel and Synapse for ESBs and Camunda and Bonitasoft for BPM, have a complicated life cycle where servers must be deployed, connector configured, logic authored in XML, and data manipulation done with XPath. This doesn’t create a good developer experience as workflows and integrations are difficult to edit, stage and test.
An alternative is to work with general purpose programming languages, like Java and JavaScript, where agility is available by using any lifecycle toolchain, but developers take responsibility for coding integration semantics. Frameworks like Spring offer an abstraction to assist with integration, but often times still require YAML, XML, and multiple separate coding files to achieve a simple integration.
Ballerina aims to fill the gap between integration products and general purpose programming languages by making it easy to write resilient programs that integrate and orchestrate across distributed endpoints.
Ballerina’s attempts to provide agility and integration simplicity simultaneously. Ballerina achieves this by packaging a language, integration syntax, and environmental deployment constructs into a single code file which is compiled into a binary, executable within a VM, and then made part of a developer’s toolchain with intellisense language servers and debuggers for IDEs.
Ballerina describes itself as, “Ballerina is a compiled, transactional, statically and strongly typed programming language with textual and graphical syntaxes. Ballerina incorporates fundamental concepts of distributed system integration into the language and offers a type safe, concurrent environment to implement microservices with distributed transactions, reliable messaging, stream processing, and workflows.”
What HelloWorld Looks LIke
For example, a Hello World API service would be written as a single text file and ran with `ballerina run <file>`:
// Packages contain functions, annotations and
// connectors. This package is referenced by
// ‘http’ namespace in the code body.
import ballerina/http;
import ballerina/io;
// A service is a network-accessible API. This
// service accessible at '/hello', and bound to a
// default listener on port 9090. `http:Service`
// is a connector in the `http` package.
service<http:Service> hello bind {port:9090} {
// A resource is an invokable API method
// Accessible at '/hello/hi’
// 'caller' is the client invoking this resource
hi (endpoint caller, http:Request request) {
// Create object to carry data back to caller
http:Response response = new;
// Objects have function calls
response.setPayload("Hello Ballerina!\n");
// Send a response back to caller
// Errors are ignored with '_'
// ‘->’ is a synchronous network-bound call
_ = caller -> respond(response);
}
}
Ballerina Design Principles
Ballerina’s language and runtime were designed to represent integration flows, development, and deployment. Integration flows, by definition, presume interaction with network-bound endpoints, and certain constructs can be incorporated into the language and runtime to simplify development.
Network Aware Type Safety - Ballerina has a structural type system with primitive, object, union, and tuple types. Network systems return messages with different payload types and errors. Ballerina’s type system embraces this variability with an approach based on union types. This typesafe model incorporates type inference at assignment provide numerous compile time integrity checks for network-bound payloads.
any anything; // can be any type
int integer = 0;
float floatingPoint = 0.0;
// constants are final instances of a type
@final float PI = 3.1415926;
boolean b = true;
string hi = "hello";
blob bl = hi.toBlob("UTF-8");
// json is a primitive
json js = {
a: "hello",
b: 5
};
// xml is a primitive
xml x = xml `<ballerina>
<supports>XML natively</supports>
</ballerina>`;
// type inference
var x = xml `<myXML/>`;
// errors are built in types
error e;
string[] stringArray = ["hi", "there"];
int[][] arrayOfArrays = [[1,2],[3,4]];
// union and tuple types
json | xml | string networkResponse;
(string, int) tuple = ("hello", 5);
() n = (); // the empty tuple is "null"
string | int stringOrInt = 5;
// maps are built in
map<boolean> myMap = {"ballerina": true};
// new types can be declared
// a "record" type is a simple structure
type myRecord { string a; int b; };
// records can be converted to/from JSON with error handling
rec | error myRec = <rec>j;
// you can re-declare existing types
type myInt int;
// objects have public and private fields, initialisers and logic
type myObj object {
public { string x; }
private { string y; }
new (string a, string b) {
x = a; y = b;
}
function getY() {
return y;
}
};
// enumerations are union types:
type aOrB "A" | "B";
aOrB myEnum = "A";
aOrB compilationError = "C"; // this won't compile due to type checking
// streams are first-class types
stream<obj> str;
// futures are built in types for asynchronous activities
future<string> f;
// functions are types and support lambda expressions
function (string, int) returns (string) func =
(string x, int i) => (string) { return "lambda"; };
Sequence Diagrammatic - Ballerina’s underlying language semantics were designed by modeling how independent parties communicate via structured interactions. Subsequently, every Ballerina program can be displayed as a sequence diagram of it’s flow with endpoints, including synchronous and asynchronous calls. Sequence diagrams are a reflection of how designers and architects think and document interconnected systems. Ballerina’s syntax is structured to let any tool or system derive a sequence diagram, and subsequently the way a developer thinks when writing Ballerina code encourages strong interaction best practices. The intellisense language servers that ship with Ballerina render services as sequence diagrams, such as this view from VS Code for an service asynchronously invoking an endpoint:
And this diagram is derived from a source file like:
import ballerina/http;
import ballerina/io;
import ballerina/runtime;
@http:ServiceConfig {
basePath:"/quote"
}
service<http:Service> AsyncInvoker bind {} {
@http:ResourceConfig {
methods:["GET"],
path:"/"
}
getQuote (endpoint caller, http:Request req) {
// ‘endpoint’ declares a connection to a networked location.
endpoint http:SimpleClient nasdaqServiceEP {
url:"http://localhost:9095"
};
io:println(" >> Invoking service asynchronounsly...");
// 'start' allows you to invoke a function or client
// connector action asynchronously. This is a remote
// invocation that returns without waiting for response.
future<http:Response | http:HttpConnectorError> f1
= start nasdaqServiceEP
-> get("/nasdaq/quote/GOOG", new);
io:println(" >> Invocation completed!"
+ " Proceed without blocking for a response.");
io:println(" >> Check for response availability...");
// ‘await` blocks until the previously started
// async function returns.
var response = await f1;
io:println(" >> Response available! ");
match response {
http:Response resp => {
string responseStr = check resp.getStringPayload();
io:println(" >> Response : "
+ responseStr);
_ = caller -> respond(resp);
}
http:HttpConnectorError err => {
io:println(err.message);
}
}
}
}
Concurrency Workers - Ballerina’s execution model is composed of lightweight parallel execution units known as workers. Workers use a full non-blocking policy where no function locks an executing thread, such as an HTTP I/O call awaiting response. These semantics manifest sequence concurrency where workers are independent concurrent actors that do not share state but can interact using messages, similar to how distributed systems pass messages over a network. Workers and fork/join language semantics abstract the underlying non-blocking approach to enable a simpler concurrency programming model.
import ballerina/io;
function main (string... args) {
worker w1 {
int i = 100;
float k = 2.34;
// Pass variables to w2
(i, k) -> w2;
json j = {};
// Receive a message from w2
j <- w2;
}
worker w2 {
int iw;
float kw;
any vW1;
// Receive a message from w1
vW1 <- w1;
(iw, kw) = check <(int, float)>vW1;
json jw = {"name":"Ballerina"};
// Send a message to w1
jw -> w1;
}
}
DevOps Ready - Over the past 15 years, best practices and expectations on the associated toolset that a language provides have evolved. Now, a language is not ready for adoption unless it includes a unit test framework, build system, dependency management and versioning, and a way to share modules of reusable code. Ballerina includes all of these subsystems as part of its core distribution so that there is no risk of community drift, which is what happens when the ecosystem needs to build tools on top of a language instead of designing it within the language.
Ballerina’s package management, dependency and versioning model is based upon learnings from Docker, Elm, and NPM. While it is possible to build and run individual Ballerina source files, packages (modules) can only be built as part of a project, which is managed by Ballerina. Every project has its own dependency cache and all packages are versioned according to semver rules. Strict rules limit dependency conflicts from applications that import packages from a central registry.
$ tree
/
.ballerina/ # Dependencies downloaded and cached locally
Ballerina.toml # Defines project build intent
my.package/ # Any folder is a package
RouterService.bal
tests/
RouterTests.bal
$ ballerina build
Pulling dependencies…
ballerinax/http [central.ballerina.io -> home repo] [====>] 56/56
ballerinax/rpc [central.ballerina.io -> home repo] [====>] 98/98
ballerinax/twitter [central.ballerina.io -> home repo] [====>] 79/79
Building binaries…
something.bal ⇒ target/something.balo
something.bal ⇒ target/something.balo
something.bal ⇒ target/something.balo
Running tests…
Test <mytest> ⇒ RUNNING … SUCCESS
Test <mytest> ⇒ RUNNING … SUCCESS
Test <mytest> ⇒ RUNNING … SUCCESS
Generating deployment artifacts…
@docker - complete 1/1
@kubernetes:ingress - complete 3/3
@kubernetes:svc - complete 3/3
@kubernetes:deployment - complete 1/1
SUCCESS
$ ballerina run
Service ready at http://192.168.1.101/customer
$ ballerina run kubernetes
Service ready at http://wso2.com:4056/customer
Environment Aware - Ballerina and its components are intended to be used within distributed, event-driven architectures. Subsequently, each service written within Ballerina is residing in an environment that may also include other services, legacy services, service meshes, orchestrators, API gateways, identity gateways, message brokers and databases.
Ballerina’s language and annotations are intentionally environment-aware, treating these other components as syntactical objects and also relationships as decorated annotations. By having the language and build system be environmentally aware of other components surrounding our service, we can generate essential artifact code ahead of CI/CD, perform data and integrity checks around network-bound payloads, and pre-package dependent but not yet deployed components as part of the Ballerina binary.
Ballerina ships with annotations that connect services to different components like API gateways, message brokers, and identity servers. Additionally, Ballerina includes customizable annotations that indicate how services should be packaged for deployment. Ecosystem vendors can add their own annotations and compiler extensions that will generate ready-to-deploy artifacts as part of the build process. By adding such annotations within the same file as the source code, a developer is able to maintain flow, get fast, incremental builds from the compiler, and get language server intellisense for defining how the service integrates in its environment.
For example, the hello world service can be converted to make a call to an unreliable external REST endpoint with a circuit breaker and have the compiler generate Kubernetes deployment artifacts:
// Packages contain functions, annotations and
// connectors. This package is referenced by
// ‘http’ namespace in the code body.
import ballerina/http;
import ballerina/io;
// A connector to a REST endpoint with a circuit
// breaker that will be compiled into the service
endpoint http:Client homer {
targets : [{url:"http://www.simpsonquotes.xyz"}],
circuitBreaker: {
failureThreshold:0,
resetTimeMillies:3000,
statusCodes:[400, 404, 500]
},
timeoutMillis:500
};
@kubernetes:Deployment {
image: "demo/home-demo",
name: "homer-demo"
}
@kubernetes:Service {
serviceType:"NodePort",
name:"homer-demo"
}
service<http:Service> hello bind {port:9090} {
hi (endpoint caller, http:Request request) {
// The ‘check’ operator will propagate any error
string name = check homer -> get (“/quote”);
http:Response response = new;
response.setPayload("Hello Ballerina!\n");
_ = caller -> respond(response);
}
}
Ballerina Central
Ballerina has launched into beta central.ballerina.io, which is a central, shared registry to discover packages of reusable code and assemble them within services built by developers.
Anyone can create an account and its free to use, following a model that is similar to how DockerHub shares images.
Ballerina packages can contain functions, connectors, annotations (such as those needed for deployment), enumerations, objects, and records.
Packages follow an <org-name>/<package-name> model. Organization names are defined within Ballerina Central for each user and for organizations. The approach to get started with creating packages and pushing them into Ballerina Central is simple, as developers can:
// Create a project with a package
// Subfolders are packages
ballerina init
// Search local repositories and Ballerina central for packages
ballerina search
ballerina pull <org-name>/<package-name>
// Push your packages up to Ballerina Central
// Kicks off an oAuth authorization flow in your browser to
// obtain a CLI key that is installed onto your system
ballerina push <org-name>/<package-name>
Ballerina’s Status
The Ballerina team has released .970, which is a significant evolution of the language’s type system, concurrency syntax, and connector structure. Additionally, this version introduces support for server connectors, built-in primitives for JSON & XML, annotation support for Docker and Kubernetes, and streaming SQL syntax. There are roughly 15,000 commits across 100 contributors made to Ballerina over the past year. Ballerina is ASL 2.0 licensed.
The project committers have launched Ballerina.io as a new home for Ballerina. It includes a playground for executing Ballerina services, 100 examples for learning the language, and guides which show how to learn integration with a full developer workflow that includes setting up an IDE, writing code, developing integration unit tests, building, deploying to Docker and Kubernetes, and observability with tracing or metrics tools.
What’s Next?
Ballerina is working towards 1.0 stability where they want to have 3-5 year language stability and backwards compatibility. The goal is to achieve the 1.0 Long Term Stability release before the end of the 2018. In the meantime, WSO2 is promising to provide commercial support, including an incremental patching mechanism for the VM, some time in the summer.
Developers can expect improvements released monthly, and the key focus for the remainder of the year will be prototyping an LLVM implementation, stabilizing the standard library of packages, increasing adoption of the ecosystem with Ballerina Central, and the inclusion of stateful services.
Developers are encouraged to learn more here where they can download the SDK and binaries. The Ballerina team will also host their first Ballerinacon, a one day learning event in July, offering it as both a physical and a virtual event. Developers can register here.
About the Author
Tyler Jewell is CEO of WSO2, the largest open source integration provider, and a partner at Toba Capital. He founded and ran Codenvy, a cloud DevOps company, which was acquired by Red Hat in 2017. As a venture investor, angel, and board member he's lead $100 million in placements for DevOps companies including WSO2, Cloudant (acquired by IBM), Sauce Labs, Sourcegraph, ZeroTurnaround (acquired by Rogewave), InfoQ, and AppHarbor (acquired by Microsoft). Previously, Tyler was at Oracle, Quest, MySQL, and BEA where he contributed to three books on Java.