亚马逊AWS官方博客

通过 TypeScript 源生成 Python、Java 和 .NET 软件库

作为构建者和开发人员,我们许多人都知道“不要重复自己”(或 DRY)这一原则,而且每天都在实践。整个运行时和编程语言已通过将该原则提升到一个更高的水平而开发,其核心理念是只需编写软件一次即可将其运行于许多不同的平台、硬件和操作系统。在本博文中,我探索了使用 TypeScript 语言编写和策划软件库的可能性,这在构建时可以通过多种其他编程语言(如 Python、Java 和 .NET/C#)生成到库中。由 AWS 开发的 jsii 的开源软件框架使这成为可能,该框架是 AWS Cloud Development Kit (AWS CDK) 中的核心架构之间之一。

AWS CDK 是一个软件开发框架,用于通过一些常用编程语言建模和预置云应用程序资源。在 AWS CDK 中,jsii 支持以 TypeScript 语言编写和维护“CDK 架构”,进而生成适合于其他语言的相同架构。Jsii 将通过 TypeScript 模块创建一个经类型批注的捆绑包,然后自动生成采用各种目标语言的惯用库(或包)。如 jsii 运行时架构中所详述,以这些目标语言生成的类型将代理至嵌入式 JavaScript VM 的调用,从而有效允许 jsii 模块“只需编写一次即可在任意位置运行”。 由于托管 JavaScript 引擎的性能和封送成本的原因,jsii 模块是生成开发和构建 AWS CDK 等工具链的理想之选,但它不适合于性能敏感型应用程序或用例。

jsii 入门

jsii 入门非常简单,只需创建一个新的 npm 包 :

npm init -y

确保满足以下额外条件才能将其更改为 jsii 模块:

"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
    "build": "jsii",
    "build:watch": "jsii -w",
    "package": "jsii-pacmak"
},
"author": {
    "name": "John Doe"
},
"repository": {
    "url": "https://github.com/aws-samples/jsii-code-samples.git"
}

将 jsii 工具链作为开发依赖项安装到包 - jsiijsii-pacmak

npm install --development jsii jsii-pacmak

将 jsii 部分安装到 package.json,并配置生成的 dotnetjavapython 模块:

"jsii": {
    "outdir": "dist",
    "targets": {
        "python": {
            "distName": "aws-jsiisamples.jsii-code-samples",
            "module": "aws_jsiisamples.jsii_code_samples"
        },
        "java": {
            "package": "software.aws.jsiisamples.jsii",
            "maven": {
                "groupId": "software.aws.jsiisamples.jsii",
                "artifactId": "jsii-code-samples"
            }
        },
        "dotnet": {
            "namespace": "AWSSamples.Jsii",
            "packageId": "AWSSamples.Jsii"
        }
    }
}

此操作应该会生成一个类似于此 jsii 代码示例的 package.json,并且还会继续集成设置,以便将 jsii 模块部署到包注册表,如 npm、PyPI、NuGet 和 Maven。

 TypeScript 限制

为了保持 jsii 模块与所有受支持语言的兼容性,jsii 编译器限制了可在所编写的 jsii 模块的公开 API 上使用的 TypeScript 语言。这可确保随后可以所有受支持的语言生成模块公开 API 的代理类型。这些限制和惯例在 GitHub 上均有记录,编译器将会生成有用的错误消息以及解决任何违规情况的说明。

总结一下,与 vanilla TypeScript 有关的限制包括:

  • 强制执行为保留字的所有受支持语言中的关键字
  • 将 TypeScript 接口视为以下任意一种接口的 jsii 惯例:
    • 行为接口 — 必须以大写字母为前缀(类似于 IFoo),或
    • 结构接口 — 必须没有方式,因此为纯数据类型。

举个例子,我们来看一个具有两个公共方式(greeter 和 Fibonacci 生成器)的简单 HelloWorld 类:

export class HelloWorld {
    public sayHello(name: string) {
        return `Hello, ${name}`;
    }

    public fibonacci(num: number) {
        let array = [0, 1];
        for (let i = 2; i < num + 1; i++) {
            array.push(array[i - 2] + array[i - 1]);
        }
        return array[num];
    }
}

本地模块与 jsii 模块的性能基准对比

以上示例库针对 JavaScript/TypeScript (npm)、Python (PyPI)、Java (Maven Central) 和 .NET/C# (NuGet) 构建和发布。我通过三种生成语言将它们与同等本地实施进行了对比,以获得性能基准对比。

首先,我们来快速了解一下本地实施:

Python

Python 实施如下所示,可以在 jsii-native-python GitHub 存储库中看到,并且已发布到 PyPI:

class HelloWorld:
    def say_hello(self, name):
        return 'Hello, ' + name

    def fibonacci(self, num):
        if num == 0:
            return 0
        elif num == 2 or num == 1:
            return 1
        else:
            return self.fibonacci(num - 2) + self.fibonacci(num - 1)

Java

Java 实施如下所示,可以在 jsii-native-java GitHub 存储库中看到,并且已发布到 Maven Central:

import java.util.ArrayList;
import java.util.Arrays;

public class HelloWorld {
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    public int fibonacci(Integer num) {
        ArrayList<Integer> array = new ArrayList<>(Arrays.asList(0, 1));
        for (int i = 2; i < num + 1; i++) {
            array.add(array.get(i - 2) + array.get(i - 1));
        }
        return array.get(num);
    }
}

C#

C# 实施如下所示,可以在 jsii-native-dotnet GitHub 存储库中看到,并且已发布到 NuGet:

public class HelloWorld
{
    public string SayHello(string name)
    {
        return $"Hello, {name}";
    }

    public int Fibonacci(int num)
    {
        var array = new List<int> {0, 1};
        for (var i = 2; i < num + 1; i++)
        {
            array.Add(array[i - 2] + array[i - 1]);
        }

        return array[num];
    }
}

现在,我们来看一下基准测试工具本身以及基准结果:

Python 基准测试工具(使用内置 timeit 模块)

 

jsii 模块 本地模块
Windows 14 ms/loop 65.7 ms/loop
macOS 6.7 ms/loop 34.2 ms/loop

出于某些原因(可能是因为我的本地 Python 实施效率低),jsii 模块比本地实施的速度快 4-5 倍。

Java 基准测试工具(使用 Java 基准测试工具 (JMH)

 

jsii 模块 本地模块
Windows 12,555.941 μs/op 12.838 μs/op
macOS 8,788.736 μs/op 4.720 μs/op

不出所料,对于 Java,本地实施比 jsii 模块快大约三个数量级。

.NET 基准测试工具(使用 BenchmarkDotNet

 

jsii 模块 本地模块
Windows 16,034.767 μs/op 4.282 μs/op
macOS 8,067.421 μs/op 2.917 μs/op

对于 .NET/C#,本地实施比 jsii 模块快大约三个数量级。

潜在使用案例

在探索 AWS CDK 时发现 jsii 之后,我对此编译器在非 CDK 使用案例中的潜力非常感兴趣。此外,多年以来,我一直在帮助维护一个开源库,该库可以将公历日期转换为Malayalam Era 日历(印度恒星月日历系统之一)。在那时候,它仅可作为适合于 JavaScript/Node.js 开发人员的 npm 包提供,但是,也有人请求将其移植到其他语言,如 .NET/C#Python。为了尝试进一步了解 jsii,我将原来的 ES6 代码库修改为与 jsii 兼容的 TypeScript,以帮助将计算繁重型库自动生成到 PythonJavaC#

对于像以上计算繁重型日历转换示例的实用工具库(其中前面部分显示的性能开销可接受),在以兼容 jsii 的 TypeScript 开发一次之后以多种语言发布可以很好地提高生产效率,这在组成更大分布式应用程序的 Polyglot 微服务已成为常态的今天更是如此。

更多语言支持

与 AWS CDK 类似,jsii 库是一个开源库,社区可以影响新功能的开发。该库的设计目标已记录在 jsii 设计原则中,并且可以参阅既定流程和手册,以添加要通过 TypeScript 源自动生成的更多语言支持。如果社区拥有足够的需求和贡献,那么,我认为未来支持其他常见语言(如 Go、Rust、Ruby 等)也并不难。

结论

如果您需要在包括 TypeScript 在内的多种不同编程语言生态系统中构建和维护软件库,并且这些软件库的性能需求正好在 jsii 生成的库可以提供的范畴内,那就马上在您的 TypeScript 库上开始使用 jsii 并自动生成 Python、Java 和 .NET 版本吧。