1. 简介
Smithy 是一种 API 描述语言,配套提供了一系列工具链,能根据 API 定义自动生成客户端和服务端代码。它允许我们用统一的方式描述 API,并基于此生成客户端和服务端实现。
本文将快速介绍 Smithy IDL(接口定义语言)及其核心工具链。
2. 什么是 Smithy?
Smithy 是一种接口定义语言,允许我们以与编程语言和通信协议无关的方式描述 API。 最初由亚马逊设计用于描述 AWS API,现已开源供其他服务使用。
通过配套工具链,我们能从 API 定义自动生成服务端和客户端 SDK 代码。 这使 Smithy 文件成为 API 的权威定义,确保所有客户端和服务端代码与定义保持一致。
Smithy 专为基于资源的 API 设计,典型场景是 HTTP + JSON,但也支持 XML 等其他序列化格式,甚至 MQTT 等 HTTP 之外的传输协议。
概念上,Smithy 类似于 OpenAPI 和 RAML,但更具规范性——强制要求 API 采用资源驱动设计。同时,Smithy 不限制传输协议和序列化方式,提供了更大灵活性。
3. Smithy 文件
我们通过编写 .smithy 文件(遵循 Smithy IDL 格式)定义 API。 这些文件通过资源、操作和服务三个核心概念描述 API:
- 资源:表示操作对象
- 操作:对资源执行的动作
- 服务:整个 API 的边界
Smithy 文件以版本声明和命名空间定义开头:
$version: "2"
// API 所属命名空间
namespace com.baeldung.smithy.books
随后定义资源、操作和服务,以及必要的数据结构(如操作的输入/输出)。
3.1. 资源
首先需要定义资源,它们代表我们要操作的数据。 使用 resource
关键字定义,包含资源标识符和属性,后续会添加操作。
例如定义书籍资源:
/// 书店中的书籍
resource Book {
identifiers: { bookId: BookId }
properties: {
title: String
author: String
isbn: String
publishedYear: Integer
}
}
@pattern("^[a-zA-Z0-9-_]+$")
string BookId
这里定义了 Book
资源:
- 通过
bookId
(类型为BookId
)唯一标识 BookId
是符合指定格式的字符串- 包含
title
、author
、isbn
和publishedYear
属性
对应的 JSON 示例:
{
"bookId": "abc123",
"title": "Head First Java, 3rd Edition: A Brain-Friendly Guide",
"author": "Kathy Sierra, Bert Bates, Trisha Gee",
"isbn": "9781491910771",
"publishedYear": 2022
}
3.2. 服务
除资源外,API 还需要服务定义。 服务代表实际处理数据的后端,可定义多个服务分别管理不同资源。
使用 service
关键字定义,包含版本号和管理的资源:
service BookManagementService {
version: "1.0"
resources: [
Book
]
}
此时 Smithy 知道存在 BookManagementService
管理 Book
资源,但尚未定义具体操作。
3.3. 生命周期操作
定义资源后,需要为其添加操作。 Smithy 支持标准生命周期操作:
create
:服务端生成 ID 创建资源put
:客户端提供 ID 创建资源read
:通过 ID 获取资源update
:通过 ID 更新资源delete
:通过 ID 删除资源list
:列出资源
操作使用 operation
关键字定义,包含输入、输出和错误类型。例如获取书籍的操作:
/// 根据ID获取书籍
@readonly
operation GetBook {
input: GetBookInput
output: GetBookOutput
errors: [
BookNotFoundException
]
}
/// 获取书籍的输入结构
structure GetBookInput {
@required
bookId: BookId
}
/// 获取书籍的输出结构
structure GetBookOutput {
@required
bookId: BookId
@required
title: String
@required
author: String
@required
isbn: String
publishedYear: Integer
}
/// 书籍未找到异常
@error("client")
structure BookNotFoundException {
@required
message: String
}
⚠️ 注意:输入/输出结构的字段需与资源定义匹配,但不必包含所有字段(如 GetBookInput
仅包含 bookId
)。
最后将操作关联到资源:
resource Book {
// ...
read: GetBook
}
3.4. 非生命周期操作
有时需要与资源生命周期无关的操作。 例如推荐书籍的操作。
定义方式与生命周期操作相同,但需使用 operations
关键字关联:
resource Book {
// ...
operations: [
RecommendBook
]
}
/// 推荐书籍
@readonly
operation RecommendBook {
input: RecommendBookInput
output: RecommendBookOutput
}
/// 推荐书籍的输入结构
structure RecommendBookInput {
@required
bookId: BookId
}
/// 推荐书籍的输出结构
structure RecommendBookOutput {
@required
bookId: BookId
@required
title: String
@required
author: String
}
4. 代码生成
编写完 Smithy 文件后,需要生成实际代码。 Smithy 提供工具可同时生成客户端 SDK 和服务端代码。
本文以 Java 生成为例,使用 Gradle 插件。 目前暂无 Maven 插件,因此必须使用 Gradle。
客户端和服务端生成依赖相同的插件:
在 settings.gradle
中添加:
pluginManagement {
plugins {
id 'software.amazon.smithy.gradle.smithy-jar' version "1.3.0"
id 'software.amazon.smithy.gradle.smithy-base' version "1.3.0"
}
}
在 build.gradle
中添加:
plugins {
id 'java-library'
id 'software.amazon.smithy.gradle.smithy-base'
}
dependencies {
smithyBuild "software.amazon.smithy.java.codegen:plugins:0.0.1"
}
tasks.named('compileJava') {
dependsOn 'smithyBuild'
}
创建 smithy-build.json
配置文件:
{
"version": "1.0",
"sources": [
"./smithy/"
]
}
4.1. 配置 API 协议
生成代码前需指定 API 协议。 本文使用 AWS restJson1 协议。
首先在服务定义添加注解:
@aws.protocols#restJson1
service BookManagementService {
// ...
}
然后为每个操作添加 HTTP 方法和 URI:
@readonly
@http(method: "GET", uri: "/books/{bookId}")
operation GetBook {
// ...
}
✅ 说明:GetBook
操作通过 GET /books/{bookId}
访问,其中 {bookId}
取自输入结构。
4.2. 生成客户端 SDK
在 smithy-build.json
中配置 java-client-codegen
插件:
{
...
"plugins": {
"java-client-codegen": {
"service": "com.baeldung.smithy.books#BookManagementService",
"namespace": "com.baeldung.smithy.books.client",
"protocol": "aws.protocols#restJson1"
},
}
}
在 build.gradle
添加依赖:
dependencies {
// ...
implementation "software.amazon.smithy.java:aws-client-restjson:0.0.1"
}
配置编译路径:
afterEvaluate {
def clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-client-codegen")
sourceSets.main.java.srcDir clientPath
}
构建后生成可直接使用的客户端 SDK:
BookManagementServiceClient client = BookManagementServiceClient.builder()
.endpointResolver(EndpointResolver.staticEndpoint("http://localhost:8888"))
.build();
GetBookOutput output = client.getBook(GetBookInput.builder().bookId("abc123").build());
assertEquals("Head First Java, 3rd Edition: A Brain-Friendly Guide", output.title());
4.3. 生成服务端存根
使用 java-server-codegen
插件生成服务端:
{
...
"plugins": {
"java-server-codegen": {
"service": "com.baeldung.smithy.books#BookManagementService",
"namespace": "com.baeldung.smithy.books.server"
},
}
}
在 build.gradle
添加依赖:
dependencies {
// ...
implementation "software.amazon.smithy.java:server-netty:0.0.1"
implementation "software.amazon.smithy.java:aws-server-restjson:0.0.1"
}
配置编译路径:
afterEvaluate {
def serverPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-server-codegen")
sourceSets.main.java.srcDir serverPath
}
生成代码包含操作接口和 DTO,需手动实现操作:
/**
* 根据ID获取书籍
*/
@SmithyGenerated
@FunctionalInterface
public interface GetBookOperation {
GetBookOutput getBook(GetBookInput input, RequestContext context);
}
实现类示例:
class GetBookOperationImpl implements GetBookOperation {
public GetBookOutput getBook(GetBookInput input, RequestContext context) {
return GetBookOutput.builder()
.bookId(input.bookId())
.title("Head First Java, 3rd Edition: A Brain-Friendly Guide")
.author("Kathy Sierra, Bert Bates, Trisha Gee")
.isbn("9781491910771")
.publishedYear(2022)
.build();
}
}
启动服务端:
Server server = Server.builder()
.endpoints(URI.create("http://localhost:8888"))
.addService(
BookManagementService.builder()
.addCreateBookOperation(new CreateBookOperationImpl())
.addGetBookOperation(new GetBookOperationImpl())
.addListBooksOperation(new ListBooksOperationImpl())
.addRecommendBookOperation(new RecommendBookOperationImpl())
.build()
)
.build();
server.start();
至此获得符合 Smithy 定义的完整 API 实现。
5. 总结
本文介绍了 Smithy 的核心功能:
- ✅ 使用 IDL 描述 API
- ✅ 自动生成客户端 SDK
- ✅ 自动生成服务端存根
Smithy 能显著提升 API 开发效率,下次设计 API 时不妨一试。所有示例代码可在 GitHub 获取。