Ballerina 微服务编程语言:最新发布版本与“Ballerina Central”简介

阅读数:418 2018 年 7 月 7 日

话题:语言 & 开发架构

核心要点

  • 该教程阐述了 Ballerina,这是一个新的编程语言和平台,它的目标在于让创建具有弹性的服务变得更加容易,这些服务会跨分布式端点进行集成和编排。
  • Ballerina .970 已经释放,语言的稳定性会不断提升,有望在 2108 年底发布 1.0;
  • Ballerina 现在托管在该站点上,包括包管理、依赖管理、版本化,Ballerina Central 上有一个全局托管的注册表,用于连接器(connector)和注解(annotation)。
  • Ballerina 的设计原则在于将集成的概念转换为一种语言,包括一个网络感知的类型系统、序列图语法、并发工作者、为“采用 DevOps 做好准备”以及环境感知。

Ballerina 是一种让微服务编程更加简单的方式,它通过简化集成 API 来实现这一点。Ballerina 是三年前由 WSO2 的架构师发起的,在与 EAI、ESB 和工作流产品构建集成时,他们遇到了各种挑战,Ballerina 则是他们对此给出的响应。

关于 Ballerina

开发一种新的编程语言和运行时技术栈并不是一件常见的事情。Ballerina 起源于项目领导者在进行项目集成时所遇到的挫败感,这种集成的工作越来越多地干扰了开发人员的常规工作流程,即周而复始地编辑、构建、测试。

基于配置的集成产品,如用于 ESB 的 Apache Camel 和 Synapse、用于 BPM 的 Bonitasoft,都有复杂的生命周期,它们必须要部署服务器、配置连接器、使用 XML 编写逻辑并使用 XPath 完成数据操作。这种方式没有带来良好的开发人员体验,因为工作流和集成难以编辑、管理启动阶段(stage)以及测试。

还有种替代方案就是使用通用的编程语言,如 Java 和 JavaScript,采用任意的生命周期工具链都能实践敏捷,但是开发人员需要编码实现集成的语义。像 Spring 这样的框架提供了一个抽象来辅助集成,但是通常情况下为了实现简单的集成,依然需要编写 YAML、XML 和多个单独的源码文件。

Ballerina 致力于填补集成产品和通用编程语言之间的空白,借助它能够很容易地编写有弹性的程序,实现跨分布式端点的集成和编排。

Ballerina 同时提供敏捷性和集成的便利性。Ballerina 通过将语言、集成语法以及环境化部署构造打包到一个代码文件中实现这一点,这个代码文件会被编译为可在 VM 中执行的二进制文件,然后利用智能感知服务和 IDE 调试器使其成为开发人员工具链的一部分。

Ballerina 是这样描述自己的:“Ballerina 是一门编译式的、事务性的、静态和强类型编程语言,具备文本和图形化语法。Ballerina 将分布式系统集成的基本概念组合到了语言之中,并且提供了一个类型安全的、并发的环境,以便于实现具备分布式事务、可靠消息传递、流处理和工作流的微服务。”

HelloWorld 是什么样子的呢?

举例来说,Hello World API服务可以写成一个简单的文本文件,并通过`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 的设计理念

Ballerina 的语言和运行时设计为描述集成流、开发和部署。按照定义,集成流假定会与绑定网络的端点进行交互,并且特定的构造可以合成到语言和运行时中,从而简化开发。

网络感知的类型安全性:Ballerina 有一个结构化的类型系统,包括原始类型、对象、联合类型(union)以及元组类型。网络系统所返回的信息会具有不同的载荷类型和错误。Ballerina 的类型系统采用基于联合类型的方式来实现这种可变性。这个类型安全的模型在赋值时添加了类型推断的功能,对于绑定网络载荷来说,这种方式能够提供强大的编译时完整性检查。 

复制代码
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"; };

序列图形化:Ballerina 底层的语言语义在设计之时就建模了相互独立的各方如何通过结构化的交互来进行通信。因此,每个 Ballerina 程序都能以序列图的形式来展现其端点的流动,其中包括同步和异步的调用。序列图反映了设计师和架构师如何思考和记录系统之间的关联的。Ballerina 的语法是结构化的,它允许任何的工具或系统生成序列图,因此,鼓励开发人员编写 Ballerina 代码时的思考方式要遵循强交互的最佳实践。Ballerina 自带的智能感知语言服务器能够将服务渲染为序列图,比如下图展现了 VS Code 中某个服务异步调用一个端点:

上图是由该源码文件衍生出来的:

复制代码
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 Worker):Ballerina 的执行模型是由一个名为工作者(worker)的轻量级并行执行单元组成的。工作者使用完全非阻塞的策略,这里不会有函数锁定一个执行中的线程,比如 HTTP I/O 调用等待响应。这些语义表明它们是序列并发的,工作者是相互独立的 actor,它们之间不会共享状态,而是通过消息进行交互,这类似于分布式系统通过网络传递消息。工作者和 fork/join 语言语义抽象了底层非阻塞的运行方式,从而让我们能够使用一个更简单的并发编程模型。

复制代码
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 就绪:在过去的 15 年间,人们对语言所提供的最佳实践和相关工具集的期望在不断发展。现在,如果某种语言不能包含单元测试框架、构建系统、依赖管理、版本化以及共享可重用代码的模块,那么可以说这种语言并没有为真正使用做好准备。Ballerina 包含了所有上述的子系统,并将其作为核心分发版本的一部分,所以不存在社区漂移(community drift),如果生态系统需要在语言之上构建工具,而不是设计在语言内部的话,将会发生这种漂移的情况。

Ballerina 的包管理、依赖和版本模型是在学习 Docker、Elm 和 NPM 的基础上实现的。尽管可以构建和运行单独的 Ballerina 源文件,但是包(模块)只能作为项目的一部分来构建,项目是由 Ballerina 管理的。每个项目有自己的依赖缓存,所有的包都按照语义(semver)规则进行版本化。严格的规则会防止应用出现依赖冲突,应用会在一个中央注册表中导入包。

复制代码
$ 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

环境感知:Ballerina 和它的组件是用于分布式、事件驱动架构的。Ballerina 中编写的每个服务都会位于一定的环境中,该环境可能还会包括其他的服务、遗留服务、服务网格、编排器、API 网关、标识网关(identity gateway)、消息代理和数据库。

Ballerina 的语言和注解设计为能够感知环境的,它会将这些其他的组件作为语法对象来处理,并将关系作为装饰过的注解来处理。语言和构建系统能够根据环境感知我们的服务相关的其他组件,这样我们就能在 CI/CD 之前生成必要的制件(artifact)、针对网络相关的负载进行数据和完整性检查并将依赖却还没有部署的组件进行预打包,使其成为 Ballerina 二进制的一部分。

Ballerina 自带的注解能够将服务连接至不同的组件,如 API 网关、消息代理以及标识服务器。除此之外,Ballerina 还包含了可定制化的注解,它们能够标记服务该如何进行打包部署。生态系统供应商可以添加自定义的注解和编译器扩展,它们将会作为构建过程的一部分用于生成可直接部署的制件。通过在同一个文件中以源码的方式添加这样的注解,开发人员就能够维护流程、通过编译器实现增量式快速构建并让语言服务器智能感知在自己的环境中定义该如何进行集成。

例如,hello world 服务可以转换为调用一个带有断路器的不可靠的外部 REST 端点,并让编译器生成 Kubernetes 部署制件:

复制代码
// 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 搭建到了 beta 版本的 central.ballerina.io 中,这是一个中心化的、共享的注册表,允许开发人员查找可重用代码的包并在他们的服务中组装这些包。

所有人都可以创建账号,它是免费使用的,遵循类似 DockerHub 共享镜像的模型。

Ballerina 的包可以包含函数、连接器、注解(比如部署所需的注解)、枚举、对象和记录。

包遵循 <org-name>/<package-name> 模型。Ballerina Central 会为每个用户和每个组织定义组织的名称。创建包并将其推送至 Ballerina Central 是非常简单的,开发人员可以按照下面的命令来实现:

复制代码
// 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 的状态

Ballerina 团队已经发布了.970,这是该语言类型系统、并发语法以及连接器结构的重要演进。另外,该版本引入了对服务器连接器的支持、对 JSON 和 XML 的内置原生支持、用于 Docker 和 Kubernetes 的注解以及流式 SQL 语法。在过去的一年中,大约有来自 100 个贡献者的 15,000 次提交。Ballerina 是基于 ASL 2.0 许可证的。

该项目的贡献者搭建了Ballerina.io,将其作为 Ballerina 的主页。它包含了一个执行 Ballerina 服务的实验场所、学习该语言的 100 个样例以及学习如何集成一个完整的开发人员工作流的指南,该指南包含了搭建 IDE、编写代码、开发集成单元测试、构建、部署至 Docker 和 Kubernetes、通过跟踪和度量指标实现可观测性。

以后会如何发展?

Ballerina 正在朝着 1.0 稳定版本而努力,他们希望能有 3 到 5 年的语言稳定性和向后兼容性,其目标是在 2018 年底之前完成 1.0 的长期稳定(Long Term Stability)发布版本。同时,WSO2 承诺提供在今年夏天提供商业支持,其中会包括针对 VM 的增量式补丁机制。

开发人员可以期待每月的功能增强发布,今年剩余的关注点包括提供一个 LLVM 实现的原型、继续稳定包的标准库、结合 Ballerina Central 推进该生态系统的使用以及增加有状态的服务。

他们鼓励开发人员通过该 站点学习更多的知识,在这里可以下载 SDK 和二进制文件。Ballerina 还会举办第一届 Ballerinacon,这是一个为期一天的学习活动,既包括实体活动,也有虚拟的活动。开发人员可以通过该网址进行注册。

关于作者

Tyler Jewell 是 WSO2 的 CEO,WSO2 是最大的开源集成提供商,他还是 Toba Capital 的合伙人。他曾经创建并管理云 DevOps 公司 Codenvy,该公司于 2017 年被 Red Hat 收购。作为风险投资人、天使投资人和董事会成员,他在 DevOps 公司方面主导了 1 亿美元的投资,包括 WSO2、Cloudant(被 IBM 收购)、 Sauce Labs、Sourcegraph、ZeroTurnaround(被 Rogewave 收购)、InfoQ 和 AppHarbor(被微软收购)。在此之前,Tyler 曾经在 Oracle、Quest、MySQL 和 BEA 工作,在 Java 领域参与出版过三本图书。

查看英文原文:Ballerina Microservices Programming Language: Introducing the Latest Release and "Ballerina Central"