领域模型:数据长什么样
今天看 SDK 的 model/ 包——这些 Java 类定义了"路由/服务/插件/消费者"在代码里的形状。它们是控制器接收、SDK 转换的中间语言,读懂字段就读懂了整个数据流转的载体。
model/ 里的 Java 类,就是翻译官手上的标准表单:前端填它、控制器收它、SDK 把它翻成 K8s 对象——它是贯穿数据流的中间语言。三个要记的点:version = 文档修订号(配乐观锁)、weight = 分流比例(多个上游权重之和必须 =100)、四作用域 = 规则贴在多宽的范围上(越窄优先级越高)。表单读懂了,整条数据流的"货物"就认全了。领域模型总览
| 实体 | 文件 | 落地为 |
|---|---|---|
| Route | model/Route.java | Ingress + 注解 |
| Service | model/Service.java | K8s Service 只读投影 |
| ServiceSource | model/ServiceSource.java | McpBridge registry 项 |
| Domain | model/Domain.java | ConfigMap(前缀 domain-) |
| TlsCertificate | model/TlsCertificate.java | Secret(kubernetes.io/tls) |
| WasmPluginInstance | model/WasmPluginInstance.java | WasmPlugin CR 的 matchRules |
| Consumer | model/consumer/Consumer.java | key-auth 插件实例 |
| AiRoute / LlmProvider | model/ai/*.java | ConfigMap + 插件 |
@Data @Builder 生成 getter/setter/builder,字段清晰。每个都带一个 validate() 做自校验(Day 04 控制器里调过)。VersionedDto 乐观锁
model/VersionedDto.java 是带 version 字段的父接口,对应 K8s 的 resourceVersion。
version(就是它在 K8s 里的当前版本号)。你改完提交时把这个 version 一起带回来,SDK 写 K8s 时用它做乐观锁——期间若别人改过,版本号变了,你的写入被拒绝,前端提示"配置已被他人修改,请刷新"。这就是昨天说的乐观锁在模型层的落点。Route 全字段
model/Route.java(字段 :46-108,validate() :110-130):
name / version / domains / path / methods / headers / urlParams
services // 上游服务(可多个,带权重)
rewrite / cors / headerControl / proxyNextUpstream // 高级能力
authConfig // 鉴权(allow list)
customConfigs / customLabels
readonly // 是否只读(非 console 创建 或 有不支持的特性)
mock/redirect/rateLimit/timeout 等字段标注了"未支持"——说明是预留位。readonly 很关键:如果这个 Ingress 不是 Console 建的、或含 Console 看不懂的注解,就标只读,前端只展示不允许编辑(避免破坏别人的配置)。readonly=true 是"这条路由被锁死不能用"。不是——它照常转发流量,只是 Console 不敢让你在界面上改它:要么它不是 Console 建的(没贴名字条),要么它含有 Console 看不懂的注解,改了怕破坏原意,于是"只读展示"。匹配条件与上游
route 子包里的辅助类型:
RoutePredicate/RoutePredicateTypeEnum:EQUAL("exact") / PRE("prefix") / REGULAR("regex"),每种带annotationPrefix(生成注解用)。KeyedRoutePredicate:带 key 的匹配(header/query 名 + 匹配方式)。UpstreamService:name / port / version / weight(0-100)。CorsConfig/RewriteConfig/HeaderControlConfig。
services=[{name:"svc-a",weight:80},{name:"svc-b",weight:20}] → 和 = 100 → 通过,80% 流量进 A、20% 进 B(灰度发布常这么配)。services=[{...weight:80},{...weight:30}] → 和 = 110 ≠ 100 → validate() 抛 ValidationException → 400,前端提示"权重之和必须为 100"。ServiceSource 类型体系
model/ServiceSource.java:ALLOWABLE_TYPES(:52-55)= nacos / nacos2 / nacos3 / zookeeper / consul / eureka / static / dns;validate()(:121-196)按类型分派专用校验器(Nacos/Consul/Static/Dns)。
WasmPluginInstance 与四作用域
model/WasmPluginInstance.java(实现 VersionedDto)核心字段 targets: Map<Scope,String>(:49)、configurations/rawConfigurations(:63-67)。作用域枚举 WasmPluginInstanceScope.java:
GLOBAL(0) / DOMAIN(10) / ROUTE(100) / SERVICE(1000) // 数字是优先级
validate() :118-139),因为"全局"和"具体某处"是互斥的表达。这个作用域模型是第 3 周插件管理的基础。👶 小白:为什么 GLOBAL 不能和 DOMAIN/ROUTE 一起配?一起写不是更灵活吗?
👨🏫 老师:因为语义打架。GLOBAL 的意思是"对所有流量一视同仁",一旦你又说"但某条路由特殊",那就已经不是"全局统一"了——该用 ROUTE 作用域去表达那个特例。所以 validate()(:118-139)强制:要么全局一刀切,要么按具体层级配,二者不能混。这是让配置"意图清晰、不自相矛盾"。
分页与校验
分页相关:model/PaginatedResult.java、model/CommonPageQuery.java、model/RoutePageQuery.java。校验统一放在各实体的 validate() 方法里,控制器在写操作前调用(见 Day 04)。
validate(),规则只写一处。今日小结 + 动手
🧠 今天你应该能回答
- 主要领域实体各落地为哪种 K8s 对象?
VersionedDto的 version 怎么配合乐观锁?- 上游 weight 的规则?
readonly什么时候为 true? - 插件的四个作用域和优先级?为什么 GLOBAL 不能与其他共存?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/sdk/src/main/java/com/alibaba/higress/sdk/model
sed -n '46,130p' Route.java
sed -n '52,60p' ServiceSource.java
cat WasmPluginInstanceScope.java
sed -n '40,139p' WasmPluginInstance.java
POST /v1/routes 一路追到 K8s 里生成的那个带注解的 Ingress,把 Controller→Service→SDK→K8s 四层串成一条完整链路。