介绍 Smithy CLI

作者: 海登·贝克 | 2023

Smithy 团队很高兴地宣布 S mithy CLI 已正式发布。 Smithy 是一种开源接口定义语言 (IDL),用于 亚马逊云科技 创建的 Web 服务。 亚马逊云科技 使用 Smithy 对服务进行建模,以多种语言生成服务器脚手架和丰富的客户端,并生成 亚马逊云科技 开发工具包。 Smithy 通过其可扩展的元模型和可插拔的设计支持在 API 上进行大规模协作。它专为支持以多种语言生成代码而构建,可以使用自定义特征进行扩展,支持自动执行 API 标准,并且与协议无关。Smithy 的设计植根于我们在亚马逊内部构建数千个服务 API 和开发复杂的 SDK 的经验。要了解更多信息,请访问s mithy.io ,并观看史密斯 首席工程师迈克尔·道林 的 介绍性演讲

目前,大多数开发人员使用 Gradle 和 smithy-gradle 插 件来构建他们的 Sm it hy 模型。 但是,开发人员需要安装 Java ,并了解 Gradle 工具的工作原理才能构建模型。对于还不熟悉这些工具的开发者来说,这过于复杂,而且有损于使用 Smithy 建模的预期体验。

使用 Smithy CLI,开发人员无需任何 Java 或 Gradle 知识即可更快地使用单个命令构建模型。Smithy CLI 现在可以在 macOS、Linux 和 Windows 平台上下载。

入门

Smithy CLI 使您能够快速迭代您的 Smithy 模型。使用此工具,您可以轻松构建模型,对模型进行临时验证,比较模型的差异并对其进行查询。要在装有 Homebrew 的 macOS 上安装 Smithy CLI ,你可以运行以下命令:

$ brew tap smithy-lang/tap
$ brew install smithy-cli

有关其他平台上的安装说明或详细的安装说明,可以查看 安装指南 。现在你已经安装了 Smithy CLI,你可以使用 --help 标志查看 “帮助 ” 信息:

$ smithy --help
Usage: smithy [-h | --help] [--version] <command> [<args>]
 
Available commands:
    validate    Validates Smithy models.
    build       Builds Smithy models and creates plugin artifacts for each projection found in smithy-build.json.
    diff        Compares two Smithy models and reports differences.
    ast         Reads Smithy models in and writes out a single JSON AST model.
    select      Queries a model using a selector.
    clean       Removes Smithy build artifacts.
    migrate     Migrate Smithy IDL models from 1.0 to 2.0 in place.

您也可以调用附加 --help 标志的命令来查看命令特定的信息:

$ smithy build --help
Usage: smithy build [--help | -h]
                    [--debug] [--quiet] [--no-color]
                    [--force-color] [--stacktrace]
                    [--logging LOG_LEVEL]
                    [--config | -c CONFIG_PATH...]
                    [--no-config] [--severity SEVERITY]
                    [--allow-unknown-traits]
                    [--output OUTPUT_PATH]
                    [--projection PROJECTION_NAME]
                    [--plugin PLUGIN_NAME] [<MODELS>]

Builds Smithy models and creates plugin artifacts for each projection found in
smithy-build.json.
...

在这篇文章中,我们将在 Smithy 官方文档中的 示例天气模型 中使用 Smithy CLI。您可以按照示例进行操作或使用自己的模型。你的本地工作空间应该如下所示:

~/weather $ tree .
.
├── model
│   ├── weather.smithy
├── smithy-build.json

建筑模型

模型 是 Smithy 语言的核心,而构建模型是 Smithy CLI 的核心功能。生成命令执行验证并生成构建工件,例如 JSON 抽象语法树 (AST) 、其他模型、代码等。

让我们来构建我们的基本示例天气模型:

~/weather $ smithy build model/

SUCCESS: Validated 240 shapes

Validated model, now starting projections...

──  source  ────────────────────────────────────────────────────────────────────
Completed projection source (240): weather/build/smithy/source

Summary: Smithy built 1 projection(s), 3 plugin(s), and 4 artifacts

查看构建工件( 恶劣天气/构建/smithy/source ),我们会根据我们的构建配置(smithy-build .json)找到几个文件:

~/weather $ tree .
.
├── build
│   └── smithy
│       └── source
│           ├── build-info
│           │   └── smithy-build-info.json   -- build metadata (projections, validation, ...)
│           ├── model
│           │   └── model.json               -- JSON AST
│           └── sources
│               ├── manifest                 -- an inventory of the build's smithy models
│               └── weather.smithy           -- a copy of our example weather model
├── model
│   └── weather.smithy
└── smithy-build.json

在这种情况下,我们的 sm ithy-build.json 中没有配置除默认值之外的构建 行为 ,因此构建示例天气模型仅呈现模型的 JSON AST。有关配置编译和编译工件的更多详细信息,请查看 s mithy-build 指南

整理和验证模型

诸如 Checksty le 和 SpotBugs 之类的代码验证工具可以帮助开发人员避免常见的陷阱和错误,同时还可以确保代码自动符合组织的标准。在 Smithy 中,验证器有助于防止样式问题和常见错误,因此您的团队可以专注于更重要的设计注意事项。

我们可以使用 选择器 来选择要对哪些形状进行自定义验证。作为最佳实践,我们希望对所有操作强制执行文档,因此我们将在模型文件中将选择器添加到验证器中:

// --- model/weather.smithy ---
$version: "2"

metadata validators = [{
    name: "EmitEachSelector"
    id: "OperationMissingDocumentation"
    message: "This operation is missing documentation"
    namespaces: ["example.weather"]
    configuration: {
        selector: """
            operation:not([trait|documentation])
        """
    }
}]

namespace example.weather
...

然后,我们将使用 validate 命令对天气模型运行 验证

~/weather $ smithy validate model/

──  DANGER  ────────────────────────────────────── OperationMissingDocumentation
Shape: example.weather#GetCity
File:  model/weather.smithy:46:1

45| @readonly
46| operation GetCity {
  | ^

This operation is missing documentation


──  DANGER  ────────────────────────────────────── OperationMissingDocumentation
Shape: example.weather#ListCities
File:  model/weather.smithy:92:1

88| // The paginated trait indicates that the operation may
89| // return truncated results.
90| @readonly
91| @paginated(items: "items")
92| operation ListCities {
  | ^

This operation is missing documentation
...

FAILURE: Validated 240 shapes (DANGER: 4)

有了这样定义自定义验证规则的能力,您可以为 Smithy 模型强制执行通用标准,并确保最佳实践得到遵守。有关在 Smithy 中进行验证的更多信息,请查看 验证 提示指南

差异模型

如果我们对模型进行更改,我们将使用 smithy diff 命令检查是否存在向后兼容性问题。 有关比较过程的更多信息,请参阅 smithy-diff。

让我们修改示例模型中 getCurrentTimeOut put 形状中的时间 成员。 首先,复制模型并重命名复制的模型(we ather-old.smith y):

$ cp model/weather.smithy weather-old.smithy

现在,更改 “新” 模型文件中的形状:

// --- model/weather.smithy ---
...
@output
structure GetCurrentTimeOutput {
    @required
    time: String
}
...

现在,让我们针对此更改进行兼容性检查:

~/weather $ smithy diff --old weather-old.smithy --new model/weather.smithy

──  DIFF  ERROR  ─────────────────────────────────────────── ChangedMemberTarget
Shape: example.weather#GetCurrentTimeOutput$time
File:  model/weather.smithy:138:5

136| structure GetCurrentTimeOutput {
···|
138|     time: String
   |     ^

The shape targeted by the member example.weather#GetCurrentTimeOutput$time
changed from smithy.api#Timestamp (timestamp) to smithy.api#String (string).
The type of the targeted shape changed from timestamp to string.

FAILURE: Validated 240 shapes (ERROR: 1)

我们通过更改已经有先前定义的形状的数据类型做出了重大更改( 错误 )。这很危险,因为模型版本之间的向后兼容性中断。使用您的旧模型的客户端将无法再安全地调用 getCurrentTim e 操作。有关安全改进模型的更多信息,请参阅 模型演化 指南。

查询模型

选择器 是查询模型中不同形状的强大方法。它们可用于通过验证 构建自定义模型验证逻辑 ,或定义 可以在何处应用某些 特征 。使用 Smithy CLI,可以简化使用和开发选择器的过程。

要查询天气模型中的所有形状,我们可以使用以下语句:

~/weather $ smithy select --selector '[id|namespace = "example.weather"]' model/
example.weather#City
example.weather#CityCoordinates
example.weather#CityCoordinates$latitude
example.weather#CityCoordinates$longitude
...
example.weather#Weather

如果你想在我们的模型中找到所有未记录的操作怎么办?我们可以使用以下语句来回答这个问题:

~/weather $ smithy select --selector 'operation:not([trait|documentation])' model/
example.weather#GetCity
example.weather#GetCurrentTime
example.weather#GetForecast
example.weather#ListCities

您可以根据需要对这个过程进行迭代,以回答有关模型的问题,一旦有了选择器,就可以在验证中使用它了。要更深入地了解 Smithy 选择器,请通读 选择 器指南。

自定义构建

构建过程最强大的功能之一是可扩展性。你可以使用 s mithy-build.json 文件自定义构建 ,根据需要添加投影或插件。让我们使用 Smithy CLI 在 TypeScript 中生成客户端,将其配置为使用 史密斯 打字稿代码生成器。 要更深入地了解代码生成,请查看 代码生成指南

让我们创建一个新的、更简单的服务模型用于演示目的。我们的工作空间应具有以下结构:

time
├── model
│   └── time.smithy      -- our new model file for the time service
└── smithy-build.json    -- a new smithy-build.json file

我们的新模型文件 time.smithy 应该包含以下代码:

// --- model/time.smithy ---
$version: "2"

namespace example.time

service Time {
    version: "0.0.1"
    operations: [GetCurrentTime]
}

/// An operation for getting the current time
@readonly
@http(code: 200, method: "GET", uri: "/time",)
operation GetCurrentTime {
    output := { 
        @required 
        @timestampFormat("date-time") 
        time: Timestamp 
    }
}

要为时间服务生成打字稿客户端,我们的构建配置文件应包含 TypeScript 插件和用于生成时间模型代码的参数:

// --- smithy-build.json ---
{
    "version": "1.0",
    "projections": {
        "source": {
            "plugins": {
                "typescript-codegen": {
                    "service": "example.time#Time",
                    "package": "@example/time",
                    "packageVersion": "0.0.1"
                }
            }
        }
    },
    "maven": {
        "dependencies": [
            "software.amazon.smithy:smithy-model:1.30.0",
            "software.amazon.smithy.typescript:smithy-typescript-codegen:0.14.0"
        ]
    }
}

该版本将应用打字稿代码生成器插件来生成代码,并将解析来自Maven的生成器的依赖关系,如配置中的mav en 部分所示。让我们构建模型并生成代码:

~/time $ smithy build model/

SUCCESS: Validated 378 shapes

Validated model, now starting projections...

[WARNING] Unable to find a protocol generator for example.time#Time: Unable to derive the protocol setting of the service `example.time#Time`
   because no protocol definition traits were present. You need to set an explicit `protocol` to generate in smithy-build.json to generate this service.
──  source  ────────────────────────────────────────────────────────────────────
Completed projection source (378): time/build/smithy/source

Summary: Smithy built 1 projection(s), 4 plugin(s), and 22 artifacts

在工作 区的 build/smithy/source/typescript-codegen 目录下为时间客户端生成了多个 TypeScript 源 文件和配置文件。因为我们的 时间 服务没有指定协议,所以打印了警告,但出于演示目的,我们可以放心地将其忽略。让我们来看看 bu ild/Smithy/Source/Typescript-Codegen/src/ Time.ts 文件中生成的代码片段:

// smithy-typescript generated code
import { TimeClient } from "./TimeClient";
import {
  GetCurrentTimeCommand,
  GetCurrentTimeCommandInput,
  GetCurrentTimeCommandOutput,
} from "./commands/GetCurrentTimeCommand";
import { HttpHandlerOptions as __HttpHandlerOptions } from "@aws-sdk/types";

export class Time extends TimeClient {
  /**
   * An operation for getting the current time
   */
  public getCurrentTime(
    args: GetCurrentTimeCommandInput,
    options?: __HttpHandlerOptions,
  ): Promise<GetCurrentTimeCommandOutput>;
  public getCurrentTime(
    args: GetCurrentTimeCommandInput,
    cb: (err: any, data?: GetCurrentTimeCommandOutput) => void
  ): void;
  public getCurrentTime(
    args: GetCurrentTimeCommandInput,
    options: __HttpHandlerOptions,
    cb: (err: any, data?: GetCurrentTimeCommandOutput) => void
  ): void;
  public getCurrentTime(
    args: GetCurrentTimeCommandInput,
    optionsOrCb?: __HttpHandlerOptions | ((err: any, data?: GetCurrentTimeCommandOutput) => void),
    cb?: (err: any, data?: GetCurrentTimeCommandOutput) => void
  ): Promise<GetCurrentTimeCommandOutput> | void {
    const command = new GetCurrentTimeCommand(args);
    if (typeof optionsOrCb === "function") {
      this.send(command, optionsOrCb)
    } else if (typeof cb === "function") {
      if (typeof optionsOrCb !== "object")
        throw new Error(`Expect http options but get ${typeof optionsOrCb}`)
      this.send(command, optionsOrCb || {}, cb)
    } else {
      return this.send(command, optionsOrCb);
    }
  }
}

从前面的代码片段中,我们可以观察到代码生成器中使用模型中的 getCurrentTime 操作在时间 服务的 TypeScript 客户端中创建了一个名为 getCurrentTim e 的方法。如果这是一项真正的服务,则客户可以使用此方法在他们自己的 TypeScript 包中向我们的服务提出请求。仅使用 Smithy CLI,我们就能够构建我们的简单时间模型,并在 TypeScript 中生成一些基本的客户端代码——要做到这一点,你需要使用 Gradle 并熟悉 Gradle 生态系统来管理你的项目。

下一步是什么?

立即开始使用 Smithy CLI 来简化使用 Smithy 构建模型时的体验。Smithy CLI 使您可以轻松构建、验证和快速迭代您的模型。

我们将不断改进 CLI 以增强开发者体验,因此,请发表评论或在 GitHub 上联系我们,告诉我们你对 Smith y CLI 的使用感受。 如果你有改进的想法,请毫不犹豫地创建 问题 拉取请求 。查看 smith y.io 上的 Smith y 文档和用户指南,详细了解 如何充分发挥 Smithy 的潜力。

作者简介:

Hayden Baker

海登·贝克·

海登是亚马逊云科技史密斯团队的软件开发工程师。他喜欢开发旨在改善开发者体验的项目和工具。你可以在 GitHub 上找到他 @haydenbaker。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。