前端开拓涉及 HTML、CSS 和原生 JavaScript, 或者一到多个 JavaScript 框架/库(如 JQuery 或 React.js)。在过去几年中,可用的工具、框架和库呈指数级增长,因此,前端开拓本身便是一个非常广泛的课题。后端开拓常日涉及做事器端脚本措辞(如 PHP 或 JSP)又或者前端利用的做事。随着单页Web 运用程序 (SPA) 的涌现,开拓职员已经摆脱了传统的做事器端脚本措辞,转而采取 API(REST、GraphQL、WebSocket 等)作为后端。持久层包括一个或多个 SQL 或者 NoSQL 数据库。但随着“大数据”的涌现,数据存储不再局限于传统的数据存储。热门的栈
MERN 栈- 图片来源
在上面提到的每一层利用的所有技能中,有一些技能比其他技能更受欢迎。当这些覆盖所有三层的技能结合在一起时,我们称之为“栈”,近些年来,我们已经看到了几个盛行的栈。
LAMP / LEMP — JavaScript 用于前端。Linux 用于托管做事,Apache/Nginx 作为 Web 做事器,MySQL 作为持久层,PHP 作为后端(或做事器端)措辞。像 Laravel 和 Symphony 这样的框架在这个栈下很盛行。MEAN — MongoDB 用于数据持久层,Express 作为后端框架,AngularJS 作为前端 JavaScript 框架,NodeJS 作为做事器运行时。MERN — 与 MEAN 栈相同,只是选择了 ReactJS 而不是 Angular。Ruby on Rails — 用 Ruby 编写的 Web 运用程序框架。Django — JavaScript 用于前端,Python Django 框架用于后端,以及相应的 SQL 或 NoSQL 数据库层。然而,栈不再那么大略了。对付给定的一层,可用的替代技能成倍增加,在给定的层中,我们可以用来组合开拓终极产品的不同技能的数量也在成倍增加。当代全栈开拓
在全栈开拓方面,SPA(单页运用程序)和 PWA(渐进式 Web 运用程序)正在成为规范,并且涌现了 SSR(做事器端渲染)等观点来办理它们的局限性。这些 Web 运用程序(前端)该当与后端 API(REST、GraphQL 等)一起事情,以便为终端用户供应终极功能。随之涌现了诸如 BFF(做事于前真个后端)之类的观点,以使后端 API 与前端用户体验 (UX) 保持同等。
BFF — 做事于前真个后端一个组织可以有多个微做事,这些做事被不同的利用方利用,如移动运用程序、Web 运用程序、其他做事/API 和外部利用方。然而,当代 Web 运用程序须要一个紧密耦合的 API 来与前端 UX 紧密合营, 因此,BFF 充当了前端和微做事之间的接口。
一个 BFF 调用多个下贱做事在前端布局一个视图。下贱 API 可以是不同的类型(REST、GraphQL gRPC 等)。阅读模式:做事于前真个后端来深入理解 BFF 架构模式。
记住上面的观点,让我们进一步谈论一下当代 Web 开拓。
全栈开拓背景下的后端开拓开拓后端 API 可能意味着两件事:
开拓 BFF — 充当前端 UX 和后端微做事之间的适配器。开拓微做事 — 开拓前端直接或间接(通过 BFF)利用的单个微做事。在全栈开拓的背景下,我们只需考虑由前端直接调用的后端 API。这些 API 可以编写为 BFF API 或单独的微做事。选择栈
如今,开拓职员不会由于一个栈很盛行就去利用它,他们选择最得当的前端技能来匹配他们希望实现的 UI/UX。然后他们选择后端技能时会考虑几个成分,包括其上市韶光、可掩护性和开拓职员的履历。
在这篇文章中,我将先容一个新的并且很有前景的后端开拓候选技能,Ballerina。在将来,当你在为全栈开拓做技能选型时,可以考虑一下它。
什么是 Ballerina?Ballerina 编程措辞徽标
Ballerina 是一种开源的云原生编程措辞,旨在简化网络做事的利用、组合和创建。Ballerina Swan Lake 是 Ballerina 措辞于今年发布的下一个紧张版本,它在所有方面都进行了重大改进,包括改进的类型系统、增强的类 SQL 措辞集成查询、增强/直不雅观的做事声明等等。
Ballerina 背后的紧张动机是让开发职员能够专注于业务逻辑,同时减少集成云原生技能所需的韶光。利用 Ballerina 的内置网络原语直接在云上定义和运行做事的办法在这场变革中发挥了关键浸染。灵巧的类型系统、面向数据的措辞集成查询、增强和直不雅观的并发处理以及内置的可不雅观察性和跟踪支持,使 Ballerina 成为云时期的首选措辞之一。
在开拓前端直接调用的 API 时,我们有几个常用的选择:
REST APIGraphQL APIWebSocket API 一旦选择了得当的 API 类型,接下来我们必须考虑以下成分:安全通信验证授权监控、可不雅观测性和日志记录除了上述成分外,我们在开拓 API 时还必须考虑可掩护性和开拓者体验。让我们看看 Ballerina 如何为开拓上述 API 类型供应支持,以及是什么让 Ballerina 成为后端 API 开拓领域中一个有前景的候选者。Ballerina 与网络交互
Ballerina 编程措辞的紧张目标之一是简化网络交互代码的编写。考虑到这一点,Ballerina 在措辞中内置了网络原语。当其他主流编程措辞都将网络视为另一种 I/O 资源时,Ballerina 为网络交互供应了更为精良的支持。为了实现这一目标,Ballerina 采取了以下精良的组件设计:
侦听器 — 充当网络层和做事层之间的接口。侦听器代表底层传输,如 HTTP、gRPC、TCP 或 WebSocket。做事—表示向终极用户公开组织功能的做事。HTTP、GraphQL、gRPC 和 WebSocket 是此类做事的一些例子。资源方法— 表示做事中的一个功能单元。例如,如果我们考虑利用一个大略的 CRUD 做事来管理库存,添加库存由一个单独的资源方法表示,而删除库存操作则由另一个资源方法表示。客户端—如今编写做事常日包括调用一个或多个外部或内部做事。例如:
在你的一项做事中,你可能想要发送一封电子邮件。为此,你须要一个电子邮件客户端。2. 同一个做事可能须要调用一个或多个内部 gRPC 做事。为此,你须要 gRPC 客户端。同样,编写做事须要调用外部做事。为此,Ballerina 有一个丰富的观点,称为客户端,外部调用由远程方法表示。在 Ballerina 运行时中调用远程方法是异步的(非壅塞,同时不须要显式回调或侦听器)。这些措辞内置的网络原语与其他措辞特性(如显式缺点处理、内置 json/xml 支持和灵巧的类型)相结合,可帮助开拓职员更快地编写直不雅观且可掩护的网络交互,这反过来又使开拓职员和组织能够更多地关注创新。Ballerina 特性一览图
让我们探索一下如何利用 Ballerina 对 REST 和 GraphQL API 的支持来编写直不雅观且故意义的后端 API。请按照入门指南安装和设置 Ballerina。
设置 Ballerina
开拓 REST API让我们看看如何利用 Ballerina 编写 REST API。
说 "Hello World!"
用 Ballerina 编写的 hello world REST API 如下所示:
import ballerina/http; service / on new http:Listener(8080) { resource function get greeting() returns string { return "Hello!"; }}
复制代码
让我们在这里解码语法的每个部分:
import ballerina/http;- 导入 ballerina/http 包。service / on new http:Listener(8080)- 在 HTTP 侦听器上创建一个高下文路径为“/”的做事,该侦听器侦听 8080 端口,做事的类型由附加的侦听器的类型决定。在当前实例中,由于我们的侦听器是 HTTP 类型的,以是它便是一个 HTTP 做事。resource function get greeting() returns string- 表示可以通过此 HTTP 做事实行单个操作。“get”是“资源访问器”。大略来说,它代表 HTTP 方法(get、post、delete 等)。“greeting”是函数名,函数名作为路径,这意味着资源路径“/greeting”由该资源方法处理。“returns string”表示此做事返回一个字符串,我们也可以在这里返回答杂的工具。return "Hello World!";- 表示资源方法的返回值。这里是字符串“Hello World”。下图显示了 Ballerina HTTP 做事语法的概述:
Ballerina HTTP 做事构造(来源)
要深入理解 Ballerina HTTP 做事语法,尤其是如何利用查询和路径参数、payload 数据绑定等,请参考以下文章:
HTTP Deep-Dive with Ballerina: Services
集成示例 — 货币转换 API以下是一个轻微繁芜一点的 REST API。给定基准货币、目标货币和金额,此 API 将返回转换后的金额。此 API 利用外部做事来获取最新汇率。
import ballerina/log;import ballerina/http; configurable int port = 8080; type ConversionResponse record { boolean success; string base; map<decimal> rates;}; service / on new http:Listener(port) { resource function get convert/[string baseCurrency]/to/[string targetCurrency](decimal amount) returns decimal|error { http:Client exchangeEP = check new ("https://api.exchangerate.host"); ConversionResponse response = check exchangeEP->get(string `/latest?base=${baseCurrency}`); if !response.success { return error("Exchange rates couldn't be obtained"); } decimal? rate = response.rates[targetCurrency]; if rate is () { return error("Couldn't determine exchange rate for target currency", targetCurrency = targetCurrency); } log:printInfo("converting currency", baseCurrency = baseCurrency, targetCurrency = targetCurrency, amount = amount); return rate amount; }}
复制代码
与 hello world 示例比较,这个示例展示了 Ballerina 一些更有趣的功能。
service / on new http:Listener(port) - 根本路径现在是 /,端口是可配置的,这意味着它可以在运行时进行配置,正如 configurable int port = 8080 中所定义的,端口的默认值为 8080,并且是可配置的,可配置变量也是一个值得把稳的特性。resource function get convert/[string baseCurrency]/to/[string targetCurrency](decimal amount) returns decimal|error - 在这种情形下,资源路径是 /convert/{baseCurrency}/to/{targetCurrency} 并且现在须要一个名为 amount 的查询参数。此资源方法会返回一个十进制值(转换速率)或一个缺点(映射到 500 - 内部做事器缺点)。ConversionResponse response = check dccClient->get(string /latest?base=${baseCurrency}) - 调用外部汇率 API,并将相应转换为公开的记录 ConversionResponse。此调用是非壅塞的。相应有效载荷被无缝地转换为了一个公开的记录,这表示了 Ballerina 的默认开放原则。运行后,如下所示的 curl 要求会将 100 美元转换为英镑。
curl http://localhost:8080/convert/USD/to/GBP?amount=100
复制代码
红利:低代码开拓Ballerina 具有无泄露的图形表示。也便是说,您可以同时编辑源代码和低代码视图(图形表示)。下图是上述 API 的低代码视图:
上述货币转换 API 的低代码视图
只管我们不会在 Ballerina 的低代码方向做过多探索,但它对付非技能或技能水平较低的人来说,这有助于他们理解和编写代码,以是也试一试吧。
无泄露 — 任何东西都可以用代码编程,代码中的统统都是可视的。
一个大略的 CRUD 做事下面是一个用 Ballerina 编写的 CRUD 做事示例,它操作一组保存在内存中的产品。
import ballerina/http;import ballerina/log;import ballerina/uuid; # 表示一种产品public type Product record {| # Product ID string id?; # Name of the product string name; # Product description string description; # Product price Price price;|}; # 表示货币的列举public enum Currency { USD, LKR, SGD, GBP} # 表示价格public type Price record {| # Currency Currency currency; # Amount decimal amount;|}; # 表示缺点public type Error record {| # Error code string code; # Error message string message;|}; # 缺点相应public type ErrorResponse record {| # Error Error 'error;|}; # 缺点的要求相应public type ValidationError record {| http:BadRequest; # Error response. ErrorResponse body;|}; # 表示已创建相应的标头public type LocationHeader record {| # Location header. A link to the created product. string location;|}; # 产品创建相应public type ProductCreated record {| http:Created; # Location header representing a link to the created product. LocationHeader headers;|}; # 产品更新相应public type ProductUpdated record {| http:Ok;|}; # 产品做事service / on new http:Listener(8080) { private map<Product> products = {}; # 列出所有产品 # + return - List of products resource function get products() returns Product[] { return self.products.toArray(); } # 添加一个新产品 # # + product - Product to be added # + return - product created response or validation error resource function post products(@http:Payload Product product) returns ProductCreated|ValidationError { if product.name.length() == 0 || product.description.length() == 0 { log:printWarn("Product name or description is not present", product = product); return <ValidationError>{ body: { 'error: { code: "INVALID_NAME", message: "Product name and description are required" } } }; } if product.price.amount < 0d { log:printWarn("Product price cannot be negative", product = product); return <ValidationError>{ body: { 'error: { code: "INVALID_PRICE", message: "Product price cannot be negative" } } }; } log:printDebug("Adding new product", product = product); product.id = uuid:createType1AsString(); self.products[<string>product.id] = product; log:printInfo("Added new product", product = product); string productUrl = string `/products/${<string>product.id}`; return <ProductCreated>{ headers: { location: productUrl } }; } # 更新一个产品 # # + product - Updated product # + return - A product updated response or an error if product is invalid resource function put product(@http:Payload Product product) returns ProductUpdated|ValidationError { if product.id is () || !self.products.hasKey(<string>product.id) { log:printWarn("Invalid product provided for update", product = product); return <ValidationError>{ body: { 'error: { code: "INVALID_PRODUCT", message: "Invalid product" } } }; } log:printInfo("Updating product", product = product); self.products[<string>product.id] = product; return <ProductUpdated>{}; } # 删除一个产品 # # + id - Product ID # + return - Deleted product or a validation error resource function delete products/[string id]() returns Product|ValidationError { if !self.products.hasKey(<string>id) { log:printWarn("Invalid product ID to be deleted", id = id); return { body: { 'error: { code: "INVALID_ID", message: "Invalud product id" } } }; } log:printDebug("Deleting product", id = id); Product removed = self.products.remove(id); log:printDebug("Deleted product", product = removed); return removed; }}
复制代码
大部分语法是不言自明的,该做事有 4 种资源方法:
列出所有产品 — GET /products添加新产品 — POST /products更新产品 — PUT /product删除产品 — DELETE /products/{id}请把稳如何定义类型来表示产品、价格和货币。然后,我们在须要实现所需模式的地方定义了相应类型,ProductCreated 表示添加产品的相应,ValidationError 表示验证中的缺点。
# 缺点的要求相应public type ValidationError record {| http:BadRequest; # Error response. ErrorResponse body;|}; # 产品创建相应public type ProductCreated record {| http:Created; # Location header representing a link to the created product. LocationHeader headers;|};
复制代码
拥有这样的模式有助于开拓职员轻松理解代码。只需查看资源方法定义,开拓职员就可以清楚地理解资源方法。什么是资源路径,须要什么查询/路径参数,有效负载是什么,以及可能的返回类型是什么。
resource function post products(@http:Payload Product product) returns ProductCreated|ValidationError { }
复制代码
这是一个 POST 要求,发送到 /products(通过查看资源方法派生),须要 Product 类型的有效负载,并返回验证缺点 (400) 或带有位置标头的 HTTP CREATED 相应 (201)。
天生 OpenAPI 规范一旦我们用 Ballerina 编写了做事,只需指向源文件即可天生 OpenAPI 规范。通过查看源代码,它将输出带有相应状态代码和模式的 OpenAPI 规范。
您可以在 OpenAPI 部分阅读更多内容:
Ballerina OpenAPI 工具天生完全的 OpenAPI 规范可帮助您天生所需的客户端。在我们的例子中,天生 JavaScript 客户端并将我们的前端与后端轻松集成。
保护做事您可以通过将 HTTP 侦听器更新为 HTTPS 侦听器来保护您的做事,如下所示。
http:ListenerSecureSocket secureSocket = { key: { certFile: "../resource/path/to/public.crt", keyFile: "../resource/path/to/private.key" }};service /hello on new http:Listener(8080, secureSocket = secureSocket) { resource function get world() returns string { return "Hello World!"; }}
复制代码
您也可以启用双向 SSL 并进行高等配置。更多信息请参阅有关 HTTP 做事安全性的 Ballerina 示例。
验证Ballerina 内置了对 3 种身份验证机制的支持。
JWT您可以供应证书文件或授权做事器的 JWK 端点 URL 并启用 JWT 署名验证。例如,如果我们要利用像 Asgardeo 这样的 IDaaS(身份即做事)来保护我们的做事,我们只需在做事中添加以下表明:
@http:ServiceConfig { auth: [ { jwtValidatorConfig: { signatureConfig: { jwksConfig: { url: "https://api.asgardeo.io/t/imeshaorg/oauth2/jwks" } } } } ]}
复制代码
此外,
您可以保护全体做事或仅保护资源路径的子集。您可以在 JWT 中验证颁发者或受众。您可以根据声明实行授权(稍后阐明)。请参阅 Ballerina 示例中的 REST API 安全部分以理解更多信息。OAuth2
与 JWT 类似,您可以利用 OAuth2 保护您的做事。有关更多详细信息,请参阅做事 - OAuth2 示例。
基本认证对付基本身份验证,有 2 个用户存储选项可用;文件和 LDAP。请参考以下示例以理解它是如何完成的:
带有文件用户存储的基本身份验证利用 LDAP 用户存储的基本身份验证Authorization
利用 OAuth2 和 JWT,您可以验证每个做事或每个资源的浸染域。在这两种情形下,您都可以指定自定义范围键,默认值为 scope。
对付 JWT,您可以利用包含用户角色(基于角色的访问掌握 — RBAC)或权限(细粒度访问掌握)的自定义声明来授权单个操作。
import ballerina/http; public type Product record {| string id?; string name; string description;|}; Product[] products = []; @http:ServiceConfig { auth: [ { jwtValidatorConfig: { issuer: "wso2", audience: "example.com", scopeKey: "permissions", signatureConfig: { jwksConfig: { url: "https://api.asgardeo.io/t/imeshaorg/oauth2/jwks" } } } } ]}service /products on new http:Listener(8080) { @http:ResourceConfig { auth: { scopes: "product:view" } } resource function get .() returns Product[] { return products; } @http:ResourceConfig { auth: { scopes: "product:create" } } resource function post .(@http:Payload Product product) returns error? { products.push(product); }}
复制代码
如上所示,/products 做事验证传入的 JWT 是否具有列出产品的 product:view 权限和创建产品的 product:create 权限。scopeKey 设置为 permissions,这是要在 JWT 中进行验证的声明的名称。此外,它还验证了发行者和受众。
客户端显然,在编写后端 API 时,您必须与外部做事进行通信,至少你须要一个数据库客户端,无论是 DB 客户端、HTTP 客户端还是 gRPC 客户端,Ballerina 都能很好地知足您的需求。最主要的是,Ballerina 中的客户端调用是非壅塞的,开拓职员无需添加任何回调或侦听器。
看看 Ballerina 的客户端有多方便:
根本 REST 客户端示例具有承载令牌身份验证的 REST 客户端具有 OAuth2 客户端凭据付与类型的客户端具有 OAuth2 JWT 不记名授权类型的客户端此外,Ballerina 对弹性有丰富的支持,请参阅以下示例以理解它们:断路器负载均衡器故障转移重试GraphQL APIs
为了使这篇文章简短,我不会深入谈论如何编写 GraphQL API,但是,与 REST API 类似,Ballerina 对 GraphQL 做事具有相同级别的支持。请参阅以下链接以理解更多信息:
一个大略的 GraphQL 示例GraphQL 中的分层资源路径GraphQL 的 JWT 身份验证请参阅 Ballerina 网站的参考示例部分,以理解更多关于编写 GraphQL 做事的信息。WebSocket APIs
我也不会深入谈论这个问题。有关编写 WebSocket 做事的更多详细信息,请参阅 Ballerina 网站的示例参考部分下的 WebSockets 和 WebSocket 安全部分。
可不雅观察性可不雅观察性是该措辞内置的关键特性之一。利用 Ballerina,您可以开箱即用地实行分布式跟踪和指标监控。
追踪分布式追踪可通过 Jaeger 和 Choreo 实现。为了将追踪发布到 Jaeger(或 Choreo),您只需在代码中添加一行导入。在运行时,您的做事将利用 Open Telemetry 标准将追踪发布到 Jaeger(或 Choreo)。
利用 Jaeger 和 Ballerina 进行分布式跟踪
日志记录Ballerina 供应的日志框架非常适宜利用 logstash 和类似的日志剖析器进行日志剖析。在编写代码时,您可以将额外的键值对通报到日志行。
log:printInfo("This is an example log line", key1 = "key1", payload = { id: 123});
复制代码
上述日志行的输出如下所示:
time = 2022-01-26T16:19:38.662+05:30 level = INFO module = "" message = "This is an example log line" key1 = "key1" payload = {"id":123}
复制代码
指标可以利用 Prometheus 和 grafana 监控实时指标。此外,还可以利用 Choreo 监控实时指标..
Choreo 中的可不雅观察性视图(来源)
与分布式跟踪类似,只需向源代码中添加一行导入,并导入一个现成的 grafana 仪表板,即可发布和监控实时指标。
Ballerina 的实时指标 Grafana 仪表板
从以下链接阅读有关 Ballerina 可不雅观察性功能的更多信息:
Prometheus、Grafana 和 Jaeger 的可不雅观察性Choreo 的可不雅观察性持久层
后端开拓的下一个紧张方面是持久层。Ballerina 拥有丰富的 SQL 和 NoSQL DB 客户端。
通过客户端进行的 DB 调用在 Ballerina 中是非壅塞的
SQL截至到目前,以下客户端是可用的:
MySQLMSSQLOraclePostgreSQL此外,Ballerina 还供应了一种非常方便的办法,可以利用 RawTemplates 编写预先准备好的语句。
mysqlClient->execute(`insert into products (product_name, price, currency) values (${product.productName}, ${product.price}, ${product.currency})`); mysqlClient->execute(`update products set product_name = ${product.productName}, price = ${product.price}, currency=${product.currency} where id=${product?.id}`);
复制代码
在上面的示例中,${<variableName>} 表示绑定到查询的变量,上面的代码在运行时作为准备好的语句实行。
Data Binding and Streams类似地,我们可以利用如下的选择查询将数据作为用户定义类型的流来获取。假设我们有如下的产品记录:
type Product record {| int id?; string productName; float price; string currency;|};
复制代码
产品表定义如下:
CREATE TABLE `products` ( `id` int NOT NULL AUTO_INCREMENT, `product_name` varchar(255) NOT NULL, `price` float DEFAULT NULL, `currency` varchar(5) DEFAULT NULL, PRIMARY KEY (`id`))
复制代码
您可以将数据作为产品记录流获取,如下所示:
stream<Product, error?> productStream = mysqlClient->query(`select id, product_name as productName, price, currency from products`);
复制代码
请把稳,记录字段名称和提取的列名称是相似的。
NoSQLMongoDBCosmosDBAWS DynamoDBRedis除了 NoSQL 的常见上风之外,Ballerina 内置的 JSON 和开放记录类型在处理非构造化或半构造化数据时会派上用场。把稳:Ballerina 的生态系统仍在完善过程中。因此,还没有功能完好的 ORM 可用。
其它值得把稳的功能内置 JSON/XML 支持正如我们已经看到的,Ballerina 内置了对主流传输格式 JSON 和 XML 的支持。利用 Ballerina,您可以在用户定义的类型和 JSON 之间无缝转换,正如我们在 HTTP 做事和 DB 示例中看到的那样。
构造类型系统Ballerina 不该用名义类型(如 Java/Kotlin),而是依赖构造类型(如 Go/TypeScript)来确定子类型。这许可开拓职员在用户定义的类型之间以及在用户定义的类型和 JSON 之间无缝转换。
静态类型Ballerina 是静态类型的。这使得 Ballerina 能够供应一组丰富的工具来编写可靠且可掩护的代码。同时,Ballerina 遵照“默认开放”的原则,您只需定义您感兴趣的内容,开放记录便是这种用法的一个例子。
显式缺点处理应显式处理缺点。正如我们在示例中看到的,客户端调用返回结果和缺点的并集。开拓职员该当键入 check 检讨缺点,并显式地处理它们。
空安全Ballerina 是空安全的。
可掩护结合以上所有方面,Ballerina 成为一种可掩护且可靠的网络编程措辞。
图形化正如我们简要看到的,Ballerina 有一个非常好的、无泄露的低代码特性。Ballerina 利用序列图来可视化网络交互,这对付技能水平较低和非技能职员编写程序和理解程序非常有用。
总结在本文中,我想大略而全面地概述一下 Ballerina 编程措辞对编写后端 API 的支持。我们先容了如何深入编写 REST API,我们还研究了如何保护做事、如何实行身份验证和授权以及天生 OpenAPI 规范。
接下来,我们简要先容了如何编写 GraphQL 和 WebSocket 做事,然后我们研究了可不雅观察性特性和持久性层支持(针对 SQL 和 NoSQL 数据库),末了,我们看了一些值得把稳的 Ballerina 特性。
我认为所谈论的内容将有助于您更多地探索 Ballerina 编程措辞,并理解它在后端 API 开拓环境中的主要性。我希望我能够确定 Ballerina 是一种真正的云原生编程措辞,它将网络原语视为一等公民。我约请你进一步探索 Ballerina 编程措辞。
感谢您关注本文,随时分享您的见地和建议。此外,如果您还有其他问题,请联系我或 Ballerina 社区。
作者先容:
Imesha Sudasingha 是 WSO2 的高等软件工程师,他还是 Apache 软件基金会的成员,现任 Apache OODT 项目管理委员会主席,他一贯是开源的持续贡献者和推动者,在多个领域拥有事情履历,包括企业集成、支付和 DevOps,他目前参与了 Ballerina 项目,通过 IDE 工具参与改进 Ballerina 用户的开拓职员体验。
原文链接:
https://www.infoq.com/articles/ballerina-fullstack-rest-api
理解更多软件开拓与干系领域知识,点击访问 InfoQ 官网:https://www.infoq.cn/,获取更多精彩内容!