Appearance
源码:labs/java/bridge-hybrid/contract-consistency-simulation/src/ContractConsistencySimulation.java
- 原始路径:
labs/java/bridge-hybrid/contract-consistency-simulation/src/ContractConsistencySimulation.java - 类型:
java
java
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 多端一致性契约仿真:契约升级的向后兼容与破坏性变更。
*
* 对应 docs 05-bridge-hybrid/03-cross-platform-consistency-design.md:
* - 单一事实源:契约只在 Schema 定义一处,多端按契约解析
* - 三个场景演示:加可选字段(兼容)/ 字段类型变更(破坏)/ 缺必填字段(破坏)
*
* 纯 Java CLI(无外部依赖,Map 模拟 JSON 对象)。
* 运行:javac -d out src/ContractConsistencySimulation.java && java -cp out ContractConsistencySimulation
*/
public class ContractConsistencySimulation {
public static void main(String[] args) {
System.out.println("========== 1. 老客户端(v1) 解析 v1 数据 ==========");
Map<String, Object> serverV1 = json("userId", "10086", "name", "Alex");
System.out.println(" 端 A (v1) 解析: " + parseV1(serverV1));
System.out.println();
System.out.println("========== 2. 契约升级:加【可选】字段(向后兼容)==========");
Map<String, Object> serverV2Optional = json(
"userId", "10086", "name", "Alex", "avatar", "https://cdn/a.png");
System.out.println(" 端 A (v1) 解析 v2 数据(未知字段被忽略): " + parseV1(serverV2Optional) + " ✅ 兼容");
System.out.println();
System.out.println("========== 3. 事故 1:字段【类型变更】(破坏性)==========");
// 服务端把 name 从 String 改成数组("支持多语言昵称"),老客户端 String 强转崩溃
Map<String, Object> serverV2TypeChanged = json(
"userId", "10086",
"name", new String[]{"Alex", "阿历克斯"});
try {
String name = (String) serverV2TypeChanged.get("name"); // 老端按 String 取
System.out.println(" 端 A (v1) 强转成功: " + name);
} catch (ClassCastException e) {
System.out.println(" 端 A (v1) 强转崩溃: ClassCastException(String[] 无法转 String)❌ 类型变更即契约破坏");
}
System.out.println();
System.out.println("========== 4. 事故 2:服务端异常返回【缺必填字段】==========");
// 服务端降级/异常时返回缺 userId 的脏数据,老端业务逻辑空指针
Map<String, Object> serverBadData = json("name", "Alex"); // 缺 userId
String userId = (String) serverBadData.get("userId");
if (userId == null) {
System.out.println(" 端 A (v1) 拿到 userId=null → 后续业务逻辑 NPE / 展示异常 ❌");
System.out.println(" 修法:服务端异常时返回统一错误码,而不是缺字段的 200");
}
System.out.println();
System.out.println("========== 6. 契约漂移 CI 强校验 ==========");
// 契约表(单一事实源):通道名 -> 版本
Map<String, Integer> contractTable = new LinkedHashMap<>();
contractTable.put("getDeviceInfo", 1);
contractTable.put("openGallery", 2);
contractTable.put("requestPayment", 1);
// 运行时注册表:开发者新增通道却忘了登记契约 → 漂移
Map<String, Integer> registered = new LinkedHashMap<>(contractTable);
registered.put("shareToChat", 1); // 注册了却没进契约表
boolean setOk = registered.keySet().equals(contractTable.keySet());
boolean verOk = contractTable.entrySet().stream()
.allMatch(e -> registered.getOrDefault(e.getKey(), -1).equals(e.getValue()));
boolean ciPass = setOk && verOk;
System.out.println(" 校验① 注册通道集合 == 契约表集合: " + (setOk ? "✅" : "❌ 漂移(shareToChat 未登记)"));
System.out.println(" 校验② 同名通道版本一致: " + (verOk ? "✅" : "❌"));
System.out.println(" CI 结果: " + (ciPass ? "通过 ✅" : "失败,阻断构建 ❌"));
System.out.println();
System.out.println("========== 7. 结论 ==========");
System.out.println(" · 加字段:必须可选(optional),旧端忽略未知字段 → 兼容");
System.out.println(" · 改类型 / 删字段 / 必填化:都是破坏性变更,需版本兜底(v1/v2 双契约)或全端同步升级");
System.out.println(" · 错误语义:异常返回错误码(如 422 + error_code),不返回缺字段脏数据");
System.out.println(" · 契约漂移:注册表与契约表不一致 / 版本不一致 → CI 强校验阻断构建");
}
/** 契约 v1 解析:只取 userId + name,忽略未知字段 */
static Map<String, Object> parseV1(Map<String, Object> data) {
Map<String, Object> result = new LinkedHashMap<>();
result.put("userId", data.get("userId"));
result.put("name", data.get("name"));
return result;
}
/** 简化 JSON 对象构造 */
static Map<String, Object> json(Object... kv) {
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < kv.length; i += 2) map.put((String) kv[i], kv[i + 1]);
return map;
}
}