资讯详情

资讯详情

AWS SDK for Java v2 标记联合(Tagged Unions)设计解读:让 AttributeValue 这类“多选一“结构成为类型安全的一等公民

AWS SDK for Java v2 标记联合Tagged Unions设计解读让 AttributeValue 这类多选一结构成为类型安全的一等公民【免费下载链接】aws-sdk-java-v2The official AWS SDK for Java - Version 2项目地址: https://gitcode.com/GitHub_Trending/aw/aws-sdk-java-v2标记联合Tagged Union是 AWS 服务模型中大量使用的结构形态——同一时刻只能设置一个成员。本指南以 aws-sdk-java-v2 仓库中的设计文档 docs/design/core/tagged-unions/README.md 为核心系统解读 SDK 2.x 为标记联合提供的fromX(...)工厂方法、type()类型枚举与visit(...)访问者模式并结合 Document 与 JsonNode 等既有实现给出可直接落地的前后对比代码。读完本文你将理解该设计的动机、API 形态、向后兼容约束以及如何用 switch 表达式或访问者模式写出更安全的联合值处理代码。背景与动机为什么需要一等公民的标记联合在 AWS 服务模型中服务团队多年来一直在定义任意时刻只允许设置一个成员的结构这就是标记联合。AWS SDK for Java 长期缺乏对这类结构的一等公民first-class支持导致客户必须自行编写临时性的匹配逻辑先判断联合值究竟是哪一种类型再分发到分支代码去处理。设计文档中给出的经典例子是 DynamoDB 的AttributeValue其简化形态如下class AttributeValue { private String s; private String n; private SdkBytes b; private ListString ss; private ListString ns; private ListSdkBytes bs; // ... }AttributeValue的s字符串、n数字、b二进制、ss字符串集合等成员互斥同一时刻只有其中一个有值。目前客户只能先逐个成员判空再决定如何处理String result; if (attributeValue.s() ! null) { result attributeValue.s(); } else if (attributeValue.n() ! null) { result attributeValue.n(); } else if (attributeValue.b() ! null) { result attributeValue.b().asUtf8String(); } else { result null; }这种手写 if-else 链既啰嗦又容易遗漏成员尤其在服务新增联合成员后更难维护。与其要求客户自己完成这一切SDK 应当提供抽象让客户轻松使用这类值。项目目标该设计项目的目标非常明确在 2.x SDK 内为既有和新引入的联合类型提供一种向后兼容backwards-compatible且对客户友好的抽象。其中向后兼容是关键约束——意味着不能破坏现有代码对builder()、s()、hasSs()等既有 API 的调用只能在其之上叠加新能力。已有先例SDK 内部的三处联合实现设计文档指出SDK 目前至少在三处已经实现了友好的标记联合其中两处在对外公共 API一处在内部实现software.amazon.awssdk.core.document.Document类型对外位于 core/sdk-core/src/main/java/software/amazon/awssdk/core/document/Document.java用于承载无固定 schema 的开放内容。事件流Event Stream类型对外例如 Lex Runtime V2 的software.amazon.awssdk.services.lexruntimev2.model.StartConversationRequestEventStream。software.amazon.awssdk.protocols.jsoncore.JsonNode类型内部位于 core/json-utils/src/main/java/software/amazon/awssdk/protocols/jsoncore/JsonNode.java是 JSON/CBOR 解析的节点抽象。Documentis/as方法族 静态工厂 访问者从源码可以印证Document的联合式设计。它被标注为SdkPublicApi和Immutable接口上同时提供了三类 API静态工厂方法fromString(String)、fromBoolean(boolean)、fromNumber(...)重载覆盖int/long/float/double/BigDecimal/BigInteger/String/SdkNumber等多种输入、fromMap(...)、fromList(...)、fromNull()。每个成员一个从某值创建的工厂正是本文联合 API 中fromX(...)的设计原型。is/as方法族isNull()、isBoolean()、isString()、isNumber()、isMap()、isList()用于判定类型asBoolean()、asString()、asNumber()、asMap()、asList()用于取值。类型不匹配时抛出UnsupportedOperationException。访问者支持R R accept(DocumentVisitor? extends R visitor)与void accept(VoidDocumentVisitor visitor)将类型分发交给访问者实现。JsonNode内部解析器的联合抽象JsonNode位于SdkProtectedApi下是 SDK 协议层解析 JSON/CBOR 时使用的内部联合抽象同样遵循先is判定、再as取值的模式isNumber()/isString()/isBoolean()/isNull()/isArray()/isObject()/isEmbeddedObject()对应asNumber()/asString()/asBoolean()/asArray()/asObject()/asEmbeddedObject()。它还通过visit(JsonNodeVisitorT visitor)支持访问者分发访问者接口 JsonNodeVisitor 为每种节点类型定义了一个visitXxx方法visitNull、visitBoolean、visitNumber、visitString、visitArray、visitObject、visitEmbeddedObject。这一枚举成员 访问者的组合正是本文联合设计在代码生成层要复用的模式。联合类型 API 定义设计文档决定遵循Document与JsonNode的模式而非事件流类型StartConversationRequestEventStream的模式——因为后者的形态无法与既有类型向后兼容。既有结构普遍拥有的方法现有结构如AttributeValue主要包含三类方法静态builder()方法用于创建结构并设置互斥成员每个联合成员的 Get 方法如s()、n()、b()每个集合或 Map 成员的 Has 方法如hasSs()用于区分空集合与未设置这两种语义不同的状态。新增方法在上述方法之外新老联合类型还将新增以下方法静态工厂方法fromN(...)用一个互斥成员初始化结构例如AttributeValue s AttributeValue.fromS(string)类型枚举方法X.Type type()用于确定联合值当前包含的类型例如AttributeValue.Type type getItemResponse.item().type()。以包含String s、String n和SdkBytes b三个成员的AttributeValue联合类型为例其公共 API 形态如下class AttributeValue { static AttributeValue.Builder builder(); static fromS(String s); static fromN(String n); static fromB(SdkBytes b); String s(); String n(); SdkBytes b(); AttributeValue.Type type(); enum Type { S, N, B, UNKNOWN_TO_SDK_VERSION } class Builder { // ... } // ... }需要注意Type枚举中的UNKNOWN_TO_SDK_VERSION成员当服务端将来引入新的联合成员而客户使用的 SDK 版本尚未生成对应的成员与枚举常量时反序列化出的类型值可归入该占位枚举避免解析失败。这也是Document/JsonNode这类开放联合能够平滑演进的关键机制。客户体验改造前后的对比设计文档给出了三组改造前后的示例代码全部来自该文档的原始内容可完整对照。创建联合类型改造前——需要先拿 Builder再设置成员AttributeValue.builder().s(foo).build()改造后——一行静态工厂直接完成AttributeValue.fromS(foo)fromX(...)将构建 设置唯一成员压缩为一个调用同时天然保证了互斥性客户不再可能也无必要在同一 Builder 上同时调用s(...)和n(...)。读取一个类型未知的字段改造前——逐成员判空的 if-else 链String result; if (attributeValue.s() ! null) { result attributeValue.s(); } else if (attributeValue.n() ! null) { result attributeValue.n(); } else if (attributeValue.b() ! null) { result attributeValue.b().asUtf8String(); } else { result null; }改造后——先查type()再用 switch 精确分发// (Java 17) String result switch (attributeValue.type()) { case S - attributeValue.s(); case N - attributeValue.n(); case B - attributeValue.b().asUtf8String(); default - null; } // Java (8-16) String result null; switch (attributeValue.type()) { case S: result attributeValue.s(); break; case N: result attributeValue.n(); break; case B: result attributeValue.b().asUtf8String(); break; }基于枚举的 switch 比判空链更健壮编译器可辅助检查分支覆盖且语义上先确定类型、再取对应成员与联合的互斥约束天然一致。示例中的asUtf8String()是SdkBytes提供的二进制转字符串能力可见每个成员分支可以按需做专属转换。使用一个类型未知的字段改造前if (attributeValue.s() ! null) { System.out.println(attributeValue.s()); } else if (attributeValue.n() ! null) { System.out.println(attributeValue.n()); } else if (attributeValue.b() ! null) { System.out.println(attributeValue.b().asUtf8String()); }改造后——提供两种等价写法访问者Visitor模式或基于type()的 switch。访问者写法attributeValue.visit(new AttributeValue.VoidVisitor() { void visitS(String s) { System.out.println(s); } void visitN(String n) { System.out.println(n); } void visitB(SdkBytes b) { System.out.println(b.asUtf8String()); } )Java 17 switch 表达式写法// (Java 17) System.out.println(switch (attributeValue.type()) { case S - attributeValue.s(); case N - attributeValue.n(); case B - attributeValue.b().asUtf8String(); default - null; }); // Java (8-16) switch (attributeValue.type()) { case S: System.out.println(attributeValue.s()); break; case N: System.out.println(attributeValue.n()); break; case B: System.out.println(attributeValue.b().asUtf8String()); break; }访问者模式的优势在于把对每种类型的处理逻辑集中到一个类里VoidVisitor表明该访问者不返回值仅执行副作用当联合成员增多时新增处理分支只需在访问者实现中补齐对应visitXxx方法编译器会给出明确的实现约束。这一能力在仓库中已有成型先例Document的accept(DocumentVisitor)/accept(VoidDocumentVisitor)与JsonNode的visit(JsonNodeVisitor)均采用同样的分发机制联合类型 API 的设计正是对这些先例的代码生成层面推广。设计状态与后续落地该设计文档在仓库中的状态标记为Proposed提议阶段归属于 docs/design/core/README.md 所列的 SDK 核心设计文档体系。这意味着文档描述的是目标 API 形态与客户体验而AttributeValue、StartConversationRequestEventStream等服务的模型类均由 codegen代码生成器依据 Smithy 模型产出——从仓库目录结构看services/dynamodb 与 services/lexruntimev2 等模块仅保留pom.xml与少量手工代码服务模型类在构建期由 codegen 生成。因此在实际使用中判断当前 SDK 版本是否已生成fromX(...)、type()与Type枚举以构建产物中的实际类签名为准若所用版本的模型类尚未包含这些 API仍可通过既有builder().s(...)写法安全使用联合值这正是向后兼容约束的意义所在——新旧写法可平滑共存迁移无需破坏性修改。从更长远看这套抽象将统一散落在Document、JsonNode、事件流类型中的联合处理模式让客户在 DynamoDB、Lex 等服务的所有联合成员场景下都能用fromX创建、type()判定、switch 或 visitor 分发彻底告别手写判空分支。【免费下载链接】aws-sdk-java-v2The official AWS SDK for Java - Version 2项目地址: https://gitcode.com/GitHub_Trending/aw/aws-sdk-java-v2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →