跳转到主要内容

关键的要点

  • Ballerina是一种新的编程语言和平台,其目标是轻松创建跨分布式端点集成和编排的弹性服务。
  • Ballerina对分布式系统原语使用编译时抽象。这为数据转换提供了类型安全,编译器可以生成一些构件,比如部署到Docker和Kubernetes的API网关。
  • Ballerina有一些关键字来表示集成概念,包括网络端点、服务、流SQL以及表、JSON和XML的原语。这些语法元素允许ide和其他工具从任何Ballerina代码生成序列图。

Ballerina是一种通过共同设计一种既敏捷又简单集成的语言和平台来简化集成和微服务编程的方法。Ballerina是在3年前由来自WSO2的架构师创建的,其目的是为了应对他们在为EAI、ESB和工作流产品(如Apache Synapse和Apache Camel)构建集成流时遇到的挑战。

在Ballerina中创建可部署为Kubernetes上可伸缩微服务的集成流是什么感觉?

本教程将创建一个服务,异步地将托管REST服务中发布的Homer Simpson引用发布到您的Twitter帐户。集成将有一个断路器处理不可靠的荷马辛普森服务,并执行有效载荷之间的数据转换。

这是芭蕾舞女演员系列的第二部分。第一篇文章“Ballerina Microservices编程语言:介绍最新版本和“Ballerina Central”提供了语言概念的介绍。

开始

请安装Ballerina并添加到您的路径中。本机平台安装程序为您管理路径设置。

我建议您使用Visual Studio代码编写代码,因为Ballerina提供了一个语言服务器扩展,它提供了丰富的推荐、智能感知和调试器。

要与您编写的服务交互,应该安装curl。

对于Kubernetes的部署,您需要安装Docker和Kubernetes。本教程使用Docker Edge,因为它的Kubernetes设置非常简单,即使对于Windows也是如此。确认kubectl get pods没有部署任何资源。

到Twitter的连接器将需要您的Twitter API密钥和令牌。创建一个Twitter账户,通过创建一个Twitter应用程序获取这四个值。将这四个值放入一个twitter.tom文件。

#芭蕾舞演员教程配置文件与Twitter secrets

# Ballerina tutorial config file with Twitter secrets

clientId = ""

clientSecret = ""

accessToken = ""

accessTokenSecret = ""

本教程序列

我们将使用迭代改进来构建集成。

  • 第1部分:创建带有POST资源的服务。我们将从客户端接收一个字符串,并将其作为响应的一部分返回。
  • 第2部分:通过调用Twitter连接器发送Tweet。我们将搜索、发现并安装一个Twitter连接器,它将用于将字符串有效负载从客户机发布到您的Twitter提要。
  • 第3部分:断路器管理荷马辛普森引用从外部服务。我们将使用GET请求将字符串源到一个不可靠的外部服务,然后添加一个断路器来处理超时和错误条件。
  • 第4部分:将具有可观察性和跟踪的服务部署到Kubernetes。

第1部分:创建带有POST资源的服务

让我们用一个可以通过后调用访问的资源创建一个服务。有效负载将包含一个我们将返回给调用者的字符串。

创建一个文件,homer.bal:

// The `http` package which is part of the standard library 
// in the default distribution. This package contains objects
// annotations, functions, and connectors which are reusable
// in code bodies below with the `http:` namespace reference.
import ballerina/http;

// Add this annotation to the service to change the base path
// from `/hello` to `/`. This annotation came from the 
// package that we imported.
@http:ServiceConfig {
  basePath: "/"
}
service<http:Service&gt; hello bind {port:9090} {
// A `service` represents an API that implements a particular
// protocol and listens against an `endpoint`. In this case,
// the service is named “hello” and bound to an anonymous
// endpoint on port 9090.

  // Add this annotation to the resource to change its path
  // and to only accept POST invocations
  @http:ResourceConfig {
      path: "/",
      methods: ["POST"]
  }
  hi (endpoint caller, http:Request request) {
  // This is a “resource” of the service. Each resource is an
  // invocable API point. An `endpoint` represents a networked
  // location. HTTP service resources all have the invoking
  // client represented as an `endpoint` passed as a parameter.
  // This service is also an `endpoint`, acting as a listener.

      // Extract the payload from the incoming request.
      // getTextPayload() returns a union type of (string | error)
      // `check` is an operator that says, “if the return is
      // a string, then assign it to the variable on the left, otherwise
      // propagate the error up the stack.
      string payload = check request.getTextPayload();
      
      // This is a data structure in the `http` package that
      // is used to preparing a payload to send in return. 
      http:Response res;
      res.setPayload("Hello "+payload+"!\n");


      // Send the payload response to the client that originally
      // invokes our resource. The `->` is special syntax that
      // indicates the invocation is a network-bound call. The
      // `_` syntax means to ignore any response or error.
      _ = caller->respond(res);
  }
}

您可以使用“run”命令同时编译和运行此服务。

$ ballerina run homer.bal

ballerina: initiating service(s) in 'homer.bal'
ballerina: started HTTP/WS endpoint 0.0.0.0:9090

$ # You can optionally build a linked executable into a .balx and run it separately
$ ballerina build homer.bal
$ ballerina run homer.balx

在另一个控制台中运行curl来执行服务。该资源通常可以通过/hello/hi访问,反映代码中提供的名称。但是,我们使用注释来修饰代码,我们的资源现在可以通过'/'访问。

$ curl -X POST -d "Ballerina" localhost:9090
Hello Ballerina!

这里有一些有趣的概念:

  • 服务是可以绑定到不同协议的第一类构造。服务是一种入口点类型,编译器将其打包到服务器中进行部署。Ballerina支持其他入口点类型,包括基于控制台的main(…)函数。
  • Ballerina支持多种协议,如WebSockets、gRPC和JMS。资源方法的签名因服务绑定到的协议而异。在芭蕾舞女演员中有很多例子。
  • 端点也是表示任何网络端点的第一类构造,无论端点是我们正在编写的服务、调用我们的客户机,还是我们将远程调用的服务。端点是具有初始化器和内嵌特定于端点类型的可调用函数的数据结构。
  • 注释可以附加到各种对象。不同的注释附加到不同的对象,如端点、服务或资源。编译器和构建系统生成工件,并根据注释改变所构建服务的行为。第三方和生态系统供应商可以添加他们自己的注释和编译器扩展。
  • 包是注解、连接器、数据结构和函数的可重用模块。标准库中有各种各样的包,但是开发人员可以创建自己的包并将其放入Ballerina注册表中。Ballerina Central是一个免费的、共享的Ballerina包注册中心。
  • Ballerina有一个强类型类型系统,其中包含各种原语,包括json、xml和table。Union和tuple类型是语言的一部分,通过提供一种类型结构,使网络编程变得更简单,这种类型结构反映了许多网络接收的有效负载可以有许多不同的形式,每一种形式都用不同的类型表示。
  • Ballerina的语法强制区分了使用点的局部调用 "."和使用箭头的网络调用 "->"。无需开发人员修改或注释代码,就可以推断出Ballerina的语法表示序列图。序列图是集成专家交流工作流结构的方式,编写Ballerina语法的开发人员能够在VS代码或Ballerina Composer(提供的与Ballerina一起提供的编辑器)中获得图形序列图。
  • Ballerina编译成自己的字节代码,并在包含自定义调度程序的自己的VM中执行。在JVM语言中,线程被映射到类。然而,Ballerina的内部线程模型使用工作者的概念来表示一个工作单元。worker是1:1映射到线程的,而Ballerina的调度器优化了线程的执行,以确保没有任何阻塞行为,除非开发人员提出请求。使用“->”的箭形语法,Ballerina的调度器释放进行调用的worker,当调用返回响应时,Ballerina的调度器会分配另一个worker来处理响应。尽管服务的客户端在执行网络调用期间阻塞,但在幕后有两个独立的worker被使用,整个系统将其视为非阻塞调用和响应。

第2部分:通过调用Twitter连接器发送Tweet

让我们从客户端获取有效负载并将其发送到您的Twitter帐户。我们必须修改我们的代码:

  • 导入并使用一个Twitter包,该包具有一个连接器,该连接器提供用于简单调用的功能。
  • 从Twitter上传递我们的个人Twitter秘密。toml配置文件到Twitter连接器。
  • 从Twitter的响应中提取任何状态信息,并将其放入我们提供给调用者的响应有效负载中。

Ballerina连接器是一种代码对象,它允许您对端点执行操作。连接器可以在共享模块中使用,可以导入到代码中。您可以在Ballerina Central (Central . Ballerina .io)或CLI上找到可用的包。

$ ballerina search twitter
Ballerina Central
=================
|NAME             | DESCRIPTION                     | DATE           | VERSION |
|-----------------| --------------------------------| ---------------| --------|
|wso2/twitter     | Connects to Twitter from Ball...| 2018-04-27-Fri | 0.9.10  |

我们可以从命令行上拉出这个包来下载它。这类似于将映像从远程注册表移动到本地机器。Ballerina维护一个存储库,它可以缓存所有下载的软件包。

$ ballerina pull wso2/twitter

更新你的homer.bal:

import ballerina/http;
import wso2/twitter;
import ballerina/config;

// twitter package defines this type of endpoint
// that incorporates the twitter API.
// We need to initialize it with OAuth data from apps.twitter.com.
// Instead of providing this confidential data in the code
// we read it from a toml file.
endpoint twitter:Client tweeter {
  clientId: config:getAsString("clientId"),
  clientSecret: config:getAsString("clientSecret"),
  accessToken: config:getAsString("accessToken"),
  accessTokenSecret: config:getAsString("accessTokenSecret"),
  clientConfig:{}  
};

@http:ServiceConfig {
  basePath: "/"
}
service<http:Service&gt; hello bind {port:9090} {

  @http:ResourceConfig {
      path: "/",
      methods: ["POST"]
  }
  hi (endpoint caller, http:Request request) {
      http:Response res;
      string payload = check request.getTextPayload();

      // transformation of request value on its way to Twitter
      if (!payload.contains("#ballerina")){payload=payload+" #ballerina";}

      twitter:Status st = check tweeter->tweet(payload);

      // transformation on the way out - generate a JSON and pass it back
      json myJson = {
          text: payload,
          id: st.id,
          agent: "ballerina"
      };

      // pass back JSON instead of text
      res.setPayload(myJson);

      _ = caller->respond(res);
  }
}

继续运行它,这一次传递一个配置文件,其中有你的令牌和秘密:

$ ballerina run --config twitter.toml demo.bal

您将执行与前面相同的curl,响应将包含Twitter状态。

$ curl -d "My new tweet" -X POST localhost:9090

{"text":"My new tweet #ballerina","id":978399924428550145,"agent":"ballerina"}

在你的推特上你应该看到:

这里有一些新的概念:

就像系统包和标准库一样,可以导入第三方包并用于提供连接器。

  • endpoint关键字用于创建使用连接器初始化的对象。连接的类型由来自导入包的twitter:Client对象提供。这个对象的初始化器需要许多参数,这些参数提供将用于Twitter的令牌和秘密。tweeter对象具有全局作用域,可以在资源调用块中使用。
  • Twitter包提供了一个可以通过Twitter连接执行的tweet函数。这是用资源块中的tweeter->tweet(…)调用的。
  • JSON和XML是该语言中的原语。我们可以使用强类型的原语将网络有效负载中的数据映射到数据结构中。对于ESB,这种性质的数据转换通常需要XPath或其他查询语言。

第三部分:断路器管理Homer Simpson Quotes引用从外部服务

让我们用另一个外部服务提供的具有HTTP REST API的荷马辛普森引用来源我们发送的tweet主体。此服务的实现是不可靠的,虽然大多数响应都是即时的,但有时响应非常慢,这可能是由于流量过大造成的。我们将添加一个断路器,如果出现错误情况或响应时间过长,该断路器将阻止我们的服务在一段时间内调用Homer Simpson API。

更新homer.bal:

import ballerina/http;
import wso2/twitter;
import ballerina/config;

// This endpoint is the connection to the external service.
// The circuit breaker is a configuration parameter of the connection.
// The circuit breaker will flip with certain error codes or a 500 ms timeout.
// The circuit breaker flips back to original state after 3 seconds.
endpoint http:Client homer {
 url: "http://www.simpsonquotes.xyz",
 circuitBreaker: {
     failureThreshold: 0,
     resetTimeMillis: 3000,
     statusCodes: [500, 501, 502]
 },
 timeoutMillis: 500
};

endpoint twitter:Client tweeter {
 clientId: config:getAsString("clientId"),
 clientSecret: config:getAsString("clientSecret"),
 accessToken: config:getAsString("accessToken"),
 accessTokenSecret: config:getAsString("accessTokenSecret"),
 clientConfig: {} 
};

@http:ServiceConfig {
 basePath: "/"
}
service<http:Service&gt; hello bind {port: 9090} {

 @http:ResourceConfig {
     path: "/",
     methods: ["POST"]
 }
 hi (endpoint caller, http:Request request) {
     http:Response res;
     
     // use var as a shorthand for http:Response | error union type
     // Compiler is smart enough to use the actual type
     var v = homer->get("/quote");

     // match is the way to provide different handling of error vs normal output
     match v {
         http:Response hResp => {

             // if proper http response use our old code
             string payload = check hResp.getTextPayload();
             if (!payload.contains("#ballerina")){payload=payload+" #ballerina";}
             twitter:Status st = check tweeter->tweet(payload);
             json myJson = {
                 text: payload,
                 id: st.id,
                 agent: "ballerina"
             };
             res.setPayload(myJson);
         }
         error err => {
             // this block gets invoked if there is error or if circuit breaker is Open
             res.setPayload("Circuit is open. Invoking default behavior.");
         }
     }
     _ = caller->respond(res);
 }
}

构建代码并运行它,但是为了执行和演示断路器,您将需要运行它多次。

$ curl -X POST localhost:9090
{"text":"Marge, don't discourage the boy! Weaseling out of things is important to learn. It's what separates us from the animals! Except the weasel. #ballerina","id":986740441532936192,"agent":"ballerina"}

$ curl -X POST localhost:9090
Circuit is open. Invoking default behavior.

$ curl -X POST localhost:9090
Circuit is open. Invoking default behavior.

$ curl -X POST localhost:9090
{"text":"It’s not easy to juggle a pregnant wife and a troubled child, but somehow I managed to fit in eight hours of TV a day.","id":978405287928348672,"agent":"Ballerina"} 

但更有趣的发现是:

  • Ballerina具有union类型,这是一种可以接受不同类型的类型。这是表示网络概念的一种好方法,因为api经常会以不同的格式为单个响应返回数据,而最好将数据表示为不同的类型。
  • Ballerina支持泛型var数据类型,可以为其分配任何值。match关键字是一个块,它将根据所提供的变量的类型执行子块。它充当一种分支逻辑,为联合类型中的每个可能类型提供不同的块。
  • 断路器被配置为连接到Homer service的一部分。这种应用断路器的方法是Ballerina如何对分布式系统原语使用编译时抽象的一个例子。

第4部分:将具有可观察性和跟踪的服务部署到Kubernetes

现在,如果没有对现代微服务平台的本地支持,它会是什么样的云本地编程语言呢?可以将注释添加到Ballerina源文件中,以触发为Docker、Kubernetes或您定义的环境创建包的构建时任务。Ballerina的注释系统是可定制的,您可以编写针对源代码树或注释操作的附加构建器扩展,以在编译后生成工件。

再一次更新homer.bal

import ballerina/http;
import wso2/twitter;
import ballerina/config;

// Add kubernetes package
import ballerinax/kubernetes;

endpoint twitter:Client tw {
  clientId: config:getAsString("clientId"),
  clientSecret: config:getAsString("clientSecret"),
  accessToken: config:getAsString("accessToken"),
  accessTokenSecret: config:getAsString("accessTokenSecret"),
  clientConfig:{}  
};

// Now instead of inline {port:9090} bind we create a separate endpoint
// We need this so we can add a Kubernetes annotation to tell the compiler
// to generate a Kubernetes service to be exposed to the outside world
@kubernetes:Service {
  serviceType: "NodePort",
  name: "ballerina-demo" 
}
endpoint http:Listener listener {
  port: 9090
};

// Instruct the compiler to generate Kubernetes deployment artifacts
// and a Docker image from this Ballerina service
@kubernetes:Deployment {
  image: "demo/ballerina-demo",
  name: "ballerina-demo"
}
// Pass our config file into the image
@kubernetes:ConfigMap {
  ballerinaConf: "twitter.toml"
}
@http:ServiceConfig {
 basePath: "/"
}
service<http:Service&gt; hello bind listener {
  @http:ResourceConfig {
      path: "/",
      methods: ["POST"]
  }
  hi (endpoint caller, http:Request request) {
    // No change to our resource code
  }
}

就是这样,让我们来构建它:

$ ballerina build demo.bal

@kubernetes:Service                      - complete 1/1

@kubernetes:ConfigMap                    - complete 1/1

@kubernetes:Docker                       - complete 3/3

@kubernetes:Deployment                   - complete 1/1

它将创建一个名为kubernetes的文件夹,其中有用于服务的部署工件和用于创建Docker图像的Dockerfile:

$ tree
.
├── demo.bal
├── demo.balx
├── kubernetes
│   ├── demo_config_map.yaml
│   ├── demo_deployment.yaml
│   ├── demo_svc.yaml
│   └── docker
│       └── Dockerfile
└── twitter.toml

你可以把它部署到Kubernetes:

$ kubectl apply -f kubernetes/
configmap "hello-ballerina-conf-config-map" created
deployment "ballerina-demo" created
service "ballerina-demo" created

让我们看看它是否正在运行,并查找哪个外部端口映射到端口9090上运行的内部服务。在本例中,端口9090在外部映射到31977:

  • $ kubectl get pods
  • NAME                              READY     STATUS    RESTARTS   AGE
  • ballerina-demo-74b6fb687c-mbrq2   1/1       Running   0          10s
  • $ kubectl get svc
  • NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)  AGE
  • ballerina-demo   NodePort    10.98.238.0   <none>        9090:31977/TCP  24s
  • kubernetes       ClusterIP   10.96.0.1     <none>        443/TCP  2d

我们可以使用外部端口调用我们的服务。

$ curl -d "Tweet from Kubernetes" -X POST  http://localhost:31977
{"text":"Tweet from Kubernetes #ballerina", "id":978399924428550145, "agent":"Ballerina"}

芭蕾舞女(Ballerina)里还有什么?

  • Ballerina的语言是健壮的,并包含用于广泛逻辑和集成概念的语法。
  • 网络类型系统。Ballerina支持数组、记录、映射、表、联合类型、可选类型、nil提升、元组、任何类型和类型转换。
  • 并发性。Ballerina的并行性发生在workers上,而您的服务和函数可以衍生workers,异步地执行函数,并使用条件fork/join语义。
  • 流媒体。允许消息传递的内存中的对象和“forever{}”块,该块使编写流SQL来处理永不结束的传入事件流成为可能。
  • 项目。在注册中心内进行依赖管理、包版本管理、构建编排和共享包管理的跨开发人员协作。
  • 集成框架。用于简化集成的扩展和构造包括重定向、查询路径、HTTP缓存、分块、相互SSL、多部分请求和响应、HTTP/s、WebSockets、基于头的路由、基于内容的路由、重试、gRPC和WebSub。
  • Testerina。提供的单元测试框架,用于对具有保证执行顺序、模拟和分组功能的服务执行测试。

关于作者

Tyler Jewell是WSO2的首席执行官,WSO2是最大的开源集成提供商,也是Toba Capital的合伙人。他创立并运营了Codenvy,一家云DevOps公司,该公司于2017年被红帽收购。作为风险投资家、天使和董事会成员,他领导了1亿美元的DevOps公司的投资,包括WSO2、Cloudant(被IBM收购)、Sauce Labs、Sourcegraph、ZeroTurnaround(被Rogewave收购)、InfoQ和AppHarbor(被微软收购)。之前,Tyler在Oracle、Quest、MySQL和BEA工作,在那里他写了三本关于Java的书。

原文:https://www.infoq.com/articles/ballerina-integration-tutorial-part-2/

本文:

讨论:请加入知识星球【快速和低代码开发】或者小号【it_training】或者QQ群【11107767】

Tags
 
知识星球
 
微信公众号
 
视频号