Day 09 / 共 20 天 · 第 2 周 SDK 与配置存储

领域模型:数据长什么样

今天看 SDK 的 model/ 包——这些 Java 类定义了"路由/服务/插件/消费者"在代码里的形状。它们是控制器接收、SDK 转换的中间语言,读懂字段就读懂了整个数据流转的载体。

📍 你在整门课的位置(第 2 周 SDK 与配置存储)
D01 全景 D02 启动 D03 分层 D04 REST D05 横切 D06 SDK D07 存储 D08 客户端 D09 模型 D10 流转
💡 一句话兜住今天(领域模型 = 翻译官的草稿纸) Day 07/08 讲了"存哪、怎么连",今天看数据在代码里长什么样。这些 model/ 里的 Java 类,就是翻译官手上的标准表单:前端填它、控制器收它、SDK 把它翻成 K8s 对象——它是贯穿数据流的中间语言。三个要记的点:version = 文档修订号(配乐观锁)、weight = 分流比例(多个上游权重之和必须 =100)、四作用域 = 规则贴在多宽的范围上(越窄优先级越高)。表单读懂了,整条数据流的"货物"就认全了。
L01

领域模型总览

实体文件落地为
Routemodel/Route.javaIngress + 注解
Servicemodel/Service.javaK8s Service 只读投影
ServiceSourcemodel/ServiceSource.javaMcpBridge registry 项
Domainmodel/Domain.javaConfigMap(前缀 domain-)
TlsCertificatemodel/TlsCertificate.javaSecret(kubernetes.io/tls)
WasmPluginInstancemodel/WasmPluginInstance.javaWasmPlugin CR 的 matchRules
Consumermodel/consumer/Consumer.javakey-auth 插件实例
AiRoute / LlmProvidermodel/ai/*.javaConfigMap + 插件
读法:这些实体大多用 Lombok 的 @Data @Builder 生成 getter/setter/builder,字段清晰。每个都带一个 validate() 做自校验(Day 04 控制器里调过)。
L02

VersionedDto 乐观锁

model/VersionedDto.java 是带 version 字段的父接口,对应 K8s 的 resourceVersion

version 字段怎么用? 你查一个路由时,返回的 Route 带着 version(就是它在 K8s 里的当前版本号)。你改完提交时把这个 version 一起带回来,SDK 写 K8s 时用它做乐观锁——期间若别人改过,版本号变了,你的写入被拒绝,前端提示"配置已被他人修改,请刷新"。这就是昨天说的乐观锁在模型层的落点。
L03

Route 全字段

model/Route.java(字段 :46-108validate() :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 看不懂的注解,改了怕破坏原意,于是"只读展示"。
L04

匹配条件与上游

route 子包里的辅助类型:

  • RoutePredicate / RoutePredicateTypeEnum:EQUAL("exact") / PRE("prefix") / REGULAR("regex"),每种带 annotationPrefix(生成注解用)。
  • KeyedRoutePredicate:带 key 的匹配(header/query 名 + 匹配方式)。
  • UpstreamService:name / port / version / weight(0-100)
  • CorsConfig / RewriteConfig / HeaderControlConfig
weight 权重是干嘛的? 一条路由可以指向多个后端服务,weight 决定流量按什么比例分配(比如 A 服务 80%、B 服务 20%,常用于灰度发布)。规则是"所有上游权重之和必须 = 100"——这个校验前后端都会做(Day 10、Day 18 会再见到)。
📝 举个例子:权重校验的通过与拒绝 services=[{name:"svc-a",weight:80},{name:"svc-b",weight:20}] → 和 = 100 → 通过,80% 流量进 A、20% 进 B(灰度发布常这么配)。
services=[{...weight:80},{...weight:30}] → 和 = 110 ≠ 100 → validate()ValidationException400,前端提示"权重之和必须为 100"。
L05

ServiceSource 类型体系

model/ServiceSource.javaALLOWABLE_TYPES:52-55)= nacos / nacos2 / nacos3 / zookeeper / consul / eureka / static / dns;validate():121-196)按类型分派专用校验器(Nacos/Consul/Static/Dns)。

"服务来源"是什么? 微服务架构里,服务实例的地址是动态变化的,靠"注册中心"管理(服务启动时向注册中心报到)。ServiceSource 就是告诉 Higress"去哪个注册中心发现服务"——支持 Nacos、Consul、Eureka、ZooKeeper 这些主流注册中心,也支持直接写死 IP(static)或域名(dns)。这是 Higress 对比原生 K8s Ingress 的一大优势:能对接非 K8s 的服务。
L06

WasmPluginInstance 与四作用域

model/WasmPluginInstance.java(实现 VersionedDto)核心字段 targets: Map<Scope,String>:49)、configurations/rawConfigurations:63-67)。作用域枚举 WasmPluginInstanceScope.java

GLOBAL(0) / DOMAIN(10) / ROUTE(100) / SERVICE(1000)  // 数字是优先级
为什么插件要分作用域? 同一个插件(比如限流)可以配在不同层级:全局(所有流量)、某个域名、某条路由、某个服务。作用域越"窄"(SERVICE > ROUTE > DOMAIN > GLOBAL),优先级数字越大,越后生效、能覆盖更宽范围的配置。GLOBAL 不能和其他作用域共存(validate() :118-139),因为"全局"和"具体某处"是互斥的表达。这个作用域模型是第 3 周插件管理的基础。
四作用域:越窄优先级越高,越后生效 GLOBAL(0) · 所有流量(最宽,最先被覆盖) DOMAIN(10) · 某个域名 ROUTE(100) · 某条路由 SERVICE(1000) · 某个服务(最窄,优先级最高) 越往下越具体 →
图注:数字越大越"具体",具体的配置能覆盖宽泛的。GLOBAL 是全局兜底,与"某个具体处"互斥。

👶 小白:为什么 GLOBAL 不能和 DOMAIN/ROUTE 一起配?一起写不是更灵活吗?

👨‍🏫 老师:因为语义打架。GLOBAL 的意思是"对所有流量一视同仁",一旦你又说"但某条路由特殊",那就已经不是"全局统一"了——该用 ROUTE 作用域去表达那个特例。所以 validate()(:118-139)强制:要么全局一刀切,要么按具体层级配,二者不能混。这是让配置"意图清晰、不自相矛盾"。

L07

分页与校验

分页相关:model/PaginatedResult.javamodel/CommonPageQuery.javamodel/RoutePageQuery.java。校验统一放在各实体的 validate() 方法里,控制器在写操作前调用(见 Day 04)。

读法:把校验放在领域模型自己身上(而不是散在控制器里),是"充血模型"的做法——"一个 Route 怎样才算合法"由 Route 自己定义,谁用它谁调 validate(),规则只写一处。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 主要领域实体各落地为哪种 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
明天预告 · Day 10(第 2 周收官)一次操作的端到端流转——从 POST /v1/routes 一路追到 K8s 里生成的那个带注解的 Ingress,把 Controller→Service→SDK→K8s 四层串成一条完整链路。
← Day 08 Day 10 · 操作流转 →