Ballerina Swan Lake:云原生编程的 10 个引人注目的语言特征
关键要点
- 更新的、专门构建的编程语言可以提供新的做事方式,比我们习惯的方式效率更高。如果利用得当,使用正确的语言和正确的抽象来解决您的问题本身就是业务的竞争优势。
- 无论是使用领域特定语言 (DSL) 还是模板,集成仍然太困难。就在最近,这份基准报告指出,由于集成固有的复杂性,许多数字化转型项目失败或受到挑战。
- Ballerina是一种开源编程语言,专为云原生编程和集成而构建。该语言旨在使其更易于使用、组合和创建网络服务,从而允许您无缝集成分布式应用程序。
- 自 2019 年发布 1.0 版本以来,Ballerina 语言取得了长足的进步。最新的Swan Lake 版本通过网络感知和灵活的类型系统进一步简化了云原生应用程序的构建和部署、用于开发服务和 API(包括 REST 、GraphQL 和 gRPC)、序列图语法、JSON 支持和内置并发性以及本文将探讨的许多其他功能。
Ballerina 的背景
集成可以被认为是一种编程,并且通常已经成为一种具有不同技术的可视化表示,这些技术试图简化和抽象复杂性。DSL 变得非常流行,因为它们为编程提供了正确的抽象,但它们也有局限性——大多数情况下,集成开发人员必须使用常规代码来解决部分问题。此外,集成编程实践已经变得孤立,以至于使用所选集成工具进行编程的开发人员必须使用另一种工具或编程语言开发其余的应用程序。并且视觉表示对于能够观察端点之间的流和交互仍然很重要。最重要的是,通过云原生工程,集成系统现在在容器中运行,
拥有一种能够提供与代码和可视化工具集成的能力的语言不是非常有用吗?这样我们就可以解决 DSL 的局限性并能够保留软件工程的最佳实践?这种想法正是导致芭蕾舞女演员项目的原因。目标是创建一种现代编程语言,将最好的编程语言、集成技术和云原生计算结合成一种文本和图形语言,针对集成进行了优化,并具有主流潜力。
让我们通过下面的列表来探索在Swan Lake Beta 版本中引入或增强的 Ballerina 语言的主要特征, 从而了解为什么 Ballerina 是创建网络服务和分布式应用程序集成的绝佳选择。
Ballerina 的主要特征
1. Ballerina 利用应用程序语言的健壮性和可扩展性解决了脚本语言的大多数用例
- 您可以认为语言的范围很广,从 Perl 或 Awk 等脚本语言到 Rust 或 C 等系统语言,中间有 Go 或 Java 等应用程序语言。程序可以用一行脚本编写,一直到编写数百万行代码。Ballerina 位于脚本语言和应用程序语言之间。
Ballerina 具有独特的功能,使其特别适合小型节目。大多数其他为小型程序设计的脚本语言与 Ballerina 有很大的不同,因为它们是动态类型的,并且没有 Ballerina 具有的独特的可伸缩性和健壮性功能。您可以使用其他脚本语言解决的前云时代的问题仍然是相关问题。除了现在,涉及网络服务;稳健性现在比以往任何时候都更加重要。使用标准脚本语言,一个 50 行的程序在几年后往往会变成一个无法维护的 1000 行程序,而且这无法扩展。Ballerina 可用于解决脚本语言程序解决的问题,但它的可扩展性更强、更健壮,并且更适合云。
2. Ballerina 是面向数据的,而不是面向对象的
在网络交互中,面向对象的方法将数据与代码捆绑在一起,这不是跨广泛分布的微服务和 API 网络发送数据的最佳方式。
在前云时代,你的 API 是对路径中库的函数调用,你可以在调用中传递对象。但是当您的 API 位于云中时,您无法执行此操作。您希望能够通过独立于代码的网络发送数据,因为您不想公开您的代码。尽管 JAVA RMI 和 CORBA 试图支持分布式对象,但是当您具有紧密耦合并且两端属于同一方时,它仍然有效。分布式对象在松散耦合的云中不起作用。Ballerina 强调与用于处理数据的任何代码无关的纯数据。虽然 Ballerina 为内部接口提供对象,但它不是面向对象的语言。
随着云中的服务越来越多,开发人员被赋予了在其代码中使用网络资源的额外责任。编程语言本身必须在此操作中提供帮助。这就是为什么 Ballerina 带有一个网络友好的类型系统,该系统具有强大的功能来处理在线数据。
JSON 是 Ballerina 中的“通用语言”。Ballerina 中的数据类型非常接近 JSON,并且一组基本的数据类型(数字、字符串、映射和数组)一对一映射到 JSON。Ballerina 的普通内存数据值几乎是内存 JSON。这允许来自线路的 JSON 有效负载立即进入语言并在没有转换或序列化的情况下进行操作。
import ballerina/io;
import ballerina/lang.value;
json j = { "x": 1, "y": 2 };
// Returns the string that represents j in JSON format.
string s = j.toJsonString();
// Parses a string in the JSON format and returns the value that
// it represents.
json j2 = check value:fromJsonString(s);
// Allows null for JSON compatibility.
json j3 = null;
public function main() {
io:println(s);
io:println(j2);
}
3. Ballerina 有一个灵活的类型系统
编程语言的类型系统允许你描述你的位是如何组合在一起的,并且已经超越了捕捉一类错误——现在这只是类型系统为你所做的一小部分。这也是提供良好 IDE 体验的重要组成部分。
脚本语言具有动态类型,应用程序语言具有传统的静态类型,如 C++ 或 Java。如前所述,Ballerina 是一种脚本语言,但它也具有应用程序语言的特性,包括静态类型系统。在静态类型语言中,在编译时检查类型兼容性。静态类型语言通常对重构更健壮,更易于调试,并有助于创建更好的语言工具。
尽管 Ballerina 的类型系统是静态的,但它比您在应用程序语言中获得的类型灵活得多。Ballerina 语言的类型系统主要是结构化的,增加了对名义类型的支持。这意味着类型兼容性是通过考虑值的结构来确定的,而不仅仅是依赖于类型的名称。这与 Java、C++ 和 C# 等具有名义类型系统的语言不同,其中类型兼容性受实际类型名称的约束。您可以根据需要多说或少说结构。有了这个,我们通过接受某些事情不会在编译时被捕获来获得简单性和灵活性。
详细地说,您可以说它类似于在 XML 模式中定义结构的方式。如果程序接收到的 XML 负载发生变化或偏差,它仍会处理它可以识别的内容。如果有效载荷中的某些变化未被识别,它不会严格到导致失败。Ballerina 的类型系统既可以用作描述网络数据的模式语言,也可以用作处理内存中值的程序的类型系统。
在此处阅读有关 Ballerina 灵活类型系统的更多信息 。
4. Ballerina 具有处理网络数据的强大功能
Ballerina 还带有一组用于处理数据的语言功能,其中集成查询功能脱颖而出。此功能允许您使用类似于 SQL 的语法查询数据,如下所示。查询表达式包含一组类似于 SQL 的子句来处理数据。它们必须以 from 子句开头,并且可以执行各种操作,例如过滤、连接、排序、限制和投影。
import ballerina/io;
type Employee record {
string firstName;
string lastName;
decimal salary;
};
public function main() {
Employee[] employees = [
{firstName: "Rachel", lastName: "Green", salary: 3000.00},
{firstName: "Monica", lastName: "Geller", salary: 4000.00},
{firstName: "Phoebe", lastName: "Buffay", salary: 2000.00},
{firstName: "Ross", lastName: "Geller", salary: 6000.00},
{firstName: "Chandler", lastName: "Bing", salary: 8000.00},
{firstName: "Joey", lastName: "Tribbiani", salary: 10000.00}
];
// Query-like expressions for list comprehensions start with from
// and end with select.
// The order by clause sorts members in employees based on the last name.
Employee[] sorted = from var e in employees
order by e.lastName ascending
select e;
io:println(sorted);
}
还有一种 Table 数据类型,可以轻松处理关系和表格数据。下面的代码创建一个包含 Employee 类型成员的表,其中每个成员都使用其姓名字段唯一标识。主函数检索键值为 John 的 Employee 并为每个员工执行加薪。
import ballerina/io;
type Employee record {
readonly string name;
int salary;
};
// Creates a table with Employee type members, where each
// member is uniquely identified using their name field.
table<Employee> key(name) t = table [
{ name: "John", salary: 100 },
{ name: "Jane", salary: 200 }
];
function increaseSalary(int n) {
// Iterates over the rows of t in the specified order.
foreach Employee e in t {
e.salary += n;
}
}
public function main() {
// Retrieves Employee with key value `John`.
Employee? e = t["John"];
io:println(e);
increaseSalary(100);
io:println(t);
}
在此处阅读有关在 Ballerina 中编写集成查询的更多信息 。
此外,Ballerina 提供了内置的 XML 支持,其功能类似于 XQuery 以及 XPath 等 XML 导航机制。这对于大量使用 XML 但不想使用特定于 XML 的语言的人特别有用,因为他们现在使用各种数据格式。
import ballerina/io;
public function main() returns error? {
xml x1 = xml `<name>Sherlock Holmes</name>`;
xml:Element x2 =
xml `<details>
<author>Sir Arthur Conan Doyle</author>
<language>English</language>
</details>`;
// `+` does concatenation.
xml x3 = x1 + x2;
io:println(x3);
xml x4 = xml `<name>Sherlock Holmes</name><details>
<author>Sir Arthur Conan Doyle</author>
<language>English</language>
</details>`;
// `==` does deep equals.
boolean eq = x3 == x4;
io:println(eq);
// `foreach` iterates over each item.
foreach var item in x4 {
io:println(item);
}
// `x[i]` gives i-th item (empty sequence if none).
io:println(x3[0]);
// `x.id` accesses required attribute named `id`:
// result is `error` if there is no such attribute
// or if `x` is not a singleton.
xml x5 = xml `<para id="greeting">Hello</para>`;
string id = check x5.id;
io:println(id);
// `x?.id` accesses optional attribute named `id`:
// result is `()` if there is no such attribute.
string? name = check x5?.name;
io:println(name is ());
// Mutate an element using `e.setChildren(x)`.
x2.setChildren(xml `<language>French</language>`);
io:println(x2);
io:println(x3);
}
除了无缝处理不同类型数据的其他功能外,Ballerina 中还有一个十进制数据类型。这些是为业务需要而设计的浮点数,例如,用于指示价格。因为在普通语言中数值是用二进制表示的,所以它们不能准确地表示所有的实数。当数字超过格式允许的数字时,剩余的数字将被忽略——数字被四舍五入,从而产生精度错误。现实世界运行在十进制数上,这就是为什么我们认为它是一种强大的能力。
import ballerina/io;
// The `decimal` type represents the set of 128-bits IEEE 754R
// decimal floating point numbers.
decimal nanos = 1d/1000000000d;
function floatSurprise() {
float f = 100.10 - 0.01;
io:println(f);
}
public function main() {
floatSurprise();
io:println(nanos);
}
5. Ballerina 本质上是并发的,并为并发提供了内置的安全性
并发是云中的一项基本要求,因为您的网络操作具有高延迟。脚本语言通常不能很好地处理并发性。像 Javascript 这样的脚本语言通常使用异步函数,这 比回调稍好 (但不是很多)。使用 Ballerina,您拥有一个更简单的编程模型,它提供了异步函数的优点,但它是一种比异步函数更直接和直观的并发方法。
在 Ballerina 中,主要的并发概念是一个链,类似于 Go 中的 goroutine。Ballerina 程序在一个或多个线程上执行。一个线程可以与其他线程同时在单独的内核上运行,也可以与其他线程一起在单个内核上抢先执行多任务。每个线程可以分为一个或多个线程,它们是语言管理的逻辑控制线程。从程序员的角度来看,一个链确实看起来像一个操作系统线程,但它不是——它更便宜、更轻量。可以在单独的操作系统线程上调度链。Go 遵循类似的方法,但没有多少编程语言这样做。事实上,大多数动态脚本语言都不支持并发。例如,Python 有一个全局锁,所以它并不真正支持并行执行。
Ballerina 中的函数可以命名为“workers”,每个线程与函数的默认 worker 和其他命名的 worker 并发运行在一个新链上,如下所示:
import ballerina/io;
public function main() {
// Code before any named workers is executed before named
// workers start.
io:println("Initializing");
final string greeting = "Hello";
// Named workers run concurrently with the function's default
// worker and other named workers.
worker A {
// Variables declared before all named workers and
// function parameters are accessible in named workers.
io:println(greeting + " from worker A");
}
worker B {
io:println(greeting + " from worker B");
}
io:println(greeting + " from function worker");
}
Ballerina 还允许链共享可变状态。通常,将并发和共享可变状态结合起来会造成数据竞争并给出错误的结果。这是动态语言通常不公开线程的原因之一。然而,芭蕾舞女演员为这个问题提供了一个很好的解决方案,并通过协作多任务处理来确保并发安全。属于同一线程的两条链不能同时运行。相反,芭蕾舞女演员合作地(而不是抢先地)将所有线程多任务处理到一个线程上以避免锁定问题。这类似于异步函数,其中一切都在单个线程上运行,但没有复杂的编程模型。一条链通过让步实现协作式多任务处理。当一个链在特定的“屈服点”屈服时,运行时调度程序可以暂停该链的执行,
我们还可以确定何时可以并行运行链——注释可用于使链在单独的线程上运行。这是因为 Ballerina 独特的类型系统可以确定服务何时锁定到足以能够安全地使用多个线程并行处理传入请求。虽然这似乎无法提供大量并行执行,但足以有效利用常见的云实例类型。
import ballerina/io;
public function main() {
// Each named worker has a "strand" (logical thread of
// control) and execution and switches between strands only at
// specific "yield" points.
worker A {
io:println("In worker A");
}
// An annotation can be used to make a strand run on a
// separate thread.
@strand {
thread: "any"
}
worker B {
io:println("In worker B");
}
io:println("In function worker");
}
6. Ballerina 有一个内在的图形视图
处理并发和网络交互是编写云程序的固有部分。在 Ballerina 中,每个程序都是一个序列图,可以自动说明分布式和并发交互。Ballerina 程序中的函数在文本语法和序列图中都有等效的表示。您可以在两个视图之间无缝切换。Ballerina 独特的图形视图并非事后才想到的。事实上,它已经深入到语言中,以便提供关于函数的网络交互及其并发使用的真正洞察力。序列图是最适合这种情况的图。
为了解释一下,Ballerina 的命名工作者(在第 5 点中讨论)和其他函数级并发特性描述了并发,而客户端和服务的语言抽象描述了网络交互。垂直线,也称为生命线,代表工作人员和远程端点。远程端点是一个客户端对象,它包含代表与远程系统的出站交互的远程方法。远程方法调用有不同的语法,大多数语言不区分远程调用和常规函数调用。水平线表示从函数的工作人员发送到另一个工作人员或从功能的工作人员发送到远程端点的消息。Ballerina 可以轻松区分这些关键方面,并向用户展示它们的高级视图,而用户无需执行任何操作。这是唯一可能的,因为图形元素从一开始就被设计到语言中。你不会在任何其他语言中得到这个!
/filters:no_upscale()/articles/ballerina-cloud-native-programming/en/resources/1image001-1631565390059.jpg)
Ballerina VSCode 插件可以从源代码动态生成序列图。要从上述 Ballerina 代码开始生成序列图,请 下载 VSCode 插件并启动图形查看器。
7. Ballerina 是云原生的,具有用于生产和使用服务以及将代码部署到云的简单模型
除了网络感知类型系统外,Ballerina 还带有用于处理网络服务的基本语法抽象。该语言还包括使用 Docker 和 Kubernetes 在云上部署 Ballerina 应用程序的内置支持。
生产服务的服务对象
Ballerina 容纳了服务的概念,并且只需三或四行 Ballerina 代码即可编写服务。Ballerina 中的服务由三者协同工作提供支持:应用程序、侦听器和库。应用程序定义服务对象并将它们附加到侦听器。监听器由图书馆提供;例如,每个协议(HTTP/GraphQL 等)都有一个监听器,它由库提供。侦听器接收网络输入,然后调用应用程序以查找服务对象。服务对象支持两种界面风格:
- 远程方法 - 以动词命名,并支持 RPC 风格
- 资源 - 以方法(如 GET)+名词命名,并支持 RESTful 风格(用于 HTTP 和 GraphQL)
因为 Ballerina 的服务方法与其独特的面向连线的类型系统相结合,所以您可以从 Ballerina 代码生成接口描述。这可以是 OpenAPI 或 GraphQL 规范。因此,您实际上可以编写常规的芭蕾舞女演员服务对象并生成您的客户端代码。这些功能的组合使云集成能够顺利进行。
import ballerina/http;
service on new http:Listener(9090) {
resource function get greeting(string name) returns string {
return "Hello, " + name;
}
}
使用远程服务的客户端对象
出站网络交互由客户端对象表示。客户端具有代表与远程系统的出站交互的远程方法。客户端对象是允许我们绘制序列图的语法元素之一。
import ballerina/email;
function main() returns error? {
email:SmtpClient sc
= check new("smtp.example.com",
"user123@example.com",
"passwd123");
check sc -> sendMessage({
to: "contact@ballerina.io",
subject: "Ballerina"
body: "Ballerina is pretty awesome!"
});
}
/filters:no_upscale()/articles/ballerina-cloud-native-programming/en/resources/1image003-1631565390059.png)
代码到云
Ballerina 支持从代码生成 Docker 和 Kubernetes 工件,无需任何额外配置。这简化了在云中开发和部署 Ballerina 代码的体验。Code to cloud 通过从代码中导出所需的值来构建容器和所需的工件。有关 更多详细信息,请参阅此 示例。
要将您的代码部署到不同的云平台,例如 AWS 和 Microsoft Azure,服务对象上的注释用于实现轻松的云部署,如下面的代码片段所示。Ballerina 编译器可以生成工件,例如 Dockerfile、Docker 映像、Kubernetes YAML 文件和无服务器函数。
例如,Ballerina 函数可以通过使用 @azure_functions:Function 注释 Ballerina 函数来部署在 Azure 中。
import ballerina/uuid;
import ballerinax/azure_functions as af;
// HTTP request/response with no authentication
@af:Function
public function hello(@af:HTTPTrigger { authLevel: "anonymous" } string payload) returns @af:HTTPOutput string|error {
return "Hello, " + payload + "!";
}
8. 明确的错误控制流程
处理错误的方法对语言设计和使用有着普遍的影响。它影响语言的一切。当您处理网络时,错误是开展业务的正常部分,尤其是在考虑 分布式计算的八个谬误时。许多前云语言,例如 Java、Javascript 和 Typescript,都使用异常作为处理错误的一种方式。但并非每种语言都遵循这种设计。Go 和 Rust 等语言根本没有例外。
除了例外,控制流是隐式的,代码更难理解和维护。当出现问题时,只是方便地抛出异常会使一切变得完全混乱。为了使错误处理工作,您必须能够查看程序并了解是否有错误发生,以及控制流将如何改变。因此,现在有一个相当强大的趋势,即消除异常并回到一种更简单的方法,其中错误是显式的,并在错误时使用正常的控制流。这种方法可以在 Go、Rust 和 Swift 中找到。Ballerina 遵循相同的方法,并允许开发人员使用带有显式错误控制流的错误数据类型。
import ballerina/io;
// Converts bytes to a string and then to an int.
function intFromBytes(byte[] bytes) returns int|error {
string|error ret = string:fromBytes(bytes);
// The is operator can be used to distinguish errors
// from other values.
if ret is error {
return ret;
} else {
return int:fromString(ret);
}
}
// The main function can return an error.
public function main() returns error? {
int|error res = intFromBytes([104, 101, 108, 108, 111]);
if res is error {
// The `check` expression is shorthand for this pattern of
// checking if a value is an error and returning that value.
return res;
} else {
io:println("result: ", res);
}
}
9. 交易作为语言特征
编写使用事务的 Ballerina 程序非常简单,因为事务是一种语言特性。Ballerina 提供的不是事务内存,而是用于分隔事务的基本语言支持。通过这种方式,您始终可以确保您的事务具有开始、回滚或提交选项。
Ballerina 程序的运行实例包括一个事务管理器。这可以在与 Ballerina 程序相同的进程中运行,也可以在单独的进程中运行(不应通过不可靠的网络连接)。事务管理器维护从每个链到事务堆栈(或在分布式上下文中,事务分支)的映射。当一个strand的事务栈非空时,我们说它处于事务模式;链的事务堆栈上最顶层的事务是该链的当前事务。
import ballerina/io;
public function main() returns error? {
// Compile-time guarantees that transactions are bracketed with
// begin and commit or rollback. Transaction statement begins
// a new transaction and executes a block.
transaction {
doStage1();
doStage2();
// Committing a transaction must be done explicitly using a commit
// statement and it may cause an error.
check commit;
}
}
function doStage1() {
io:println("Stage1 completed");
}
function doStage2() {
io:println("Stage2 completed");
}
Ballerina 中的事务还与它的网络交互功能(即客户端和服务)组合在一起,以支持分布式事务。用户可以通过将服务的资源/远程方法和客户端对象的远程方法声明为事务性来创建客户端和服务之间的事务流。
10. 许多功能都很熟悉,芭蕾舞女演员是“包括电池”
语言的激增确实表明个人愿意学习新的语言。但是企业似乎不愿意采用一种新语言,因为他们担心无法雇用熟悉该语言的人。重要的是要强调的是,虽然 Ballerina 提供了更好的做事方式,但它还附带了 C 系列语言程序员熟悉的功能子集,足以让开发人员在几个小时或更短的时间内开始使用。
流行的 C 系列语言(C、C++、Java、JavaScript、C#、TypeScript)有很多共同点,Ballerina 通过以相同的方式做很多事情来利用这一点。如果您是一位对任何 C 系列语言具有一定编程经验的程序员,那么使用 Ballerina 进行编码将非常简单。并且除了强大的语言功能,Ballerina 是“内含电池”,这意味着该语言自带丰富的 标准库 (带有网络数据、消息传递和通信协议的库)、 包管理系统、结构化文档、测试框架,以及流行 IDE(特别是 Visual Studio Code)的扩展/插件以及其他支持该语言的工具。
结论
虽然 Ballerina 具有现代编程语言的所有通用功能,但它的优势在于它独特地提供了语言功能,使云更易于使用、组合和创建网络服务。开发人员现在可以构建弹性、安全和高性能的服务来解决分布式计算的谬误,并通过简单地使用专门用于执行此操作的编程语言来集成它们以创建云原生应用程序。
有关在 Ballerina 中创建和使用 HTTP 服务的精彩快速介绍,请查看此 截屏视频。如果您从示例中学习得更好,可以在此处找到大量突出 Ballerina 功能和概念的代码示例 。
有关 Ballerina Swan Lake 语言特性的深入介绍性解释,请观看 Ballerina 首席语言设计师 James Clark 的这个 视频系列。您还可以查看他的博客文章,了解 有关 Ballerina 设计原则的更多 背景信息。