Day 06 / 共 20 天 · 第 2 周 SDK 与配置存储
SDK 抽象:不依赖 Spring 的核心库
第 2 周开始钻 backend/sdk——这是整个后端的"发动机"。今天先看它的整体设计:一个门面接口、一条手写装配链、一个 Builder 配置对象。理解了这三样,后面读任何 SDK service 都不迷路。
📍 你在整门课的位置(第 2 周 SDK 与配置存储 · 开篇)
D01 全景→
D02 启动→
D03 分层→
D04 REST→
D05 横切→
D06 SDK→
D07 存储→
D08 客户端→
D09 模型→
D10 流转
💡 一句话兜住今天(SDK = 可单独拆下来的发动机)
第 1 周把驾驶舱(console/Spring 那套)看完了;第 2 周掀开引擎盖看发动机(sdk)。今天先看它的三大总成:门面
HigressServiceProvider = 中控台(对外只留一个入口,想要哪个零件从它身上取)、手写依赖注入 = 自己按顺序拼装零件(不靠 Spring 的自动装配,作者亲手 new 并串起来)、Builder 配置 = 一张可勾选的配置单。关键卖点:这台发动机零 Spring 依赖,能整台拆下来装到别的车上(任何 Java 程序都能 import)。L01
SDK 是什么
backend/sdk(artifactId higress-admin-sdk)封装了所有"与 Higress/K8s 打交道"的核心逻辑:K8s 客户端、模型转换、各资源 Service(Route/Service/Domain/WasmPlugin/AI…)。
SDK 为什么刻意不依赖 Spring?
console 模块用 Spring,但 sdk 模块纯 Java、零 Spring 依赖。为什么?这样任何 Java 程序(不只 Console)都能直接
import 这个 SDK 来操作 Higress 配置——比如写个命令行工具、写个自动化脚本。把"业务能力"和"Web 框架"解耦,SDK 就成了可复用的资产。Spring 装配只发生在 console 的 SdkConfig(Day 02)。L02
门面 HigressServiceProvider
sdk/service/HigressServiceProvider.java:28-61 是整个 SDK 的"总入口":
public interface HigressServiceProvider {
static HigressServiceProvider create(HigressServiceConfig config) { // :30-32 工厂
return new HigressServiceProviderImpl(config);
}
KubernetesClientService kubernetesClientService(); // 一堆 getter
RouteService routeService();
WasmPluginService wasmPluginService();
AiRouteService aiRouteService();
LlmProviderService llmProviderService();
McpServerService mcpServerService();
// ... domain/service/serviceSource/tlsCertificate/consumer ...
}
门面模式(Facade):不管 SDK 内部有多少个类、怎么互相依赖,外部只需拿到这一个
HigressServiceProvider,通过它的 getter 取到任何 service。复杂性被藏在门面后面。📝 举个例子:拿到发动机后怎么用
HigressServiceConfig cfg = HigressServiceConfig.builder().withKubeConfigPath("~/.kube/config").build(); →
HigressServiceProvider p = HigressServiceProvider.create(cfg); → 之后要操作路由就 p.routeService().add(route),要操作插件就 p.wasmPluginService()...。调用方从头到尾只碰 p 这一个对象,内部 20 多个类怎么 new、谁依赖谁,完全不用管。图注:调用方 → 门面 → 各 service;发动机内部的装配复杂度对外完全透明。
L03
手写依赖注入
实现类 HigressServiceProviderImpl.java:51-77 的构造函数是一段"手写依赖注入"——不用 Spring,自己 new 并串起来:
// :52 唯一的 K8s 出口
kubernetesClientService = new KubernetesClientService(config);
// :53 领域模型 ↔ K8s 对象转换器
kubernetesModelConverter = new KubernetesModelConverter(kubernetesClientService);
// :67 路由服务依赖:client + converter + 插件实例服务 + 消费者服务
routeService = new RouteServiceImpl(k8sClient, converter, wasmInstanceSvc, consumerSvc);
// :71-74 AI 两个服务互相引用,用 setter 回填
llmProviderService = new LlmProviderServiceImpl(...);
aiRouteService = new AiRouteServiceImpl(...);
llmProviderService.setAiRouteService(aiRouteService); // 打破循环依赖
"依赖注入"其实很朴素
Spring 帮你自动
new 对象并塞进需要它的地方,这叫依赖注入。这里没有 Spring,作者就自己按顺序 new:先造底层的(K8s 客户端),再把它传给上层的(路由服务)。看这段构造函数,就能看清"谁依赖谁"的完整拓扑——比 Spring 的注解魔法还直观。:71-74 那个 setter 回填是为了解决"两个服务互相需要对方"的循环依赖。🔧 如果让你写,你大概会这样(简化版 → 真实版)
你的朴素版:
真实版多出来的,都是为了解决具体问题:
① 先
②
③ 末尾
routeService = new RouteServiceImpl(new KubernetesClientService(config)); —— 直接 new。真实版多出来的,都是为了解决具体问题:
① 先
new KubernetesClientService 再传给别人 → 保证全 SDK 共用一个 K8s 出口,不重复建连接;②
RouteServiceImpl 还收 converter/插件服务/消费者服务 → 因为"建路由"要顺带写鉴权和插件(Day 03 的编排);③ 末尾
llmProviderService.setAiRouteService(...) 的 setter 回填 → 两个服务互相依赖,构造时无法同时 new 好,只能先造再回填。👶 小白:既然 console 已经用 Spring 了,SDK 为什么不也用注解自动装配,省得手写这一大段?
👨🏫 老师:因为 SDK 想做成"谁都能用的发动机"。一旦它 import 了 Spring,别人要用就被迫拖进整个 Spring 全家桶。保持零框架依赖,一个写命令行工具的人也能直接拿去用。代价就是这段"手写 DI"——但它反而让"谁依赖谁"一目了然,比注解魔法更好读。
L04
HigressServiceConfig(Builder)
sdk/config/HigressServiceConfig.java 用 @Data + 内部 Builder(:75-179)。关键字段(:31-61):
kubeConfigPath / kubeConfigContent // 连 K8s 的两种方式
controllerNamespace / controllerWatchedNamespace / controllerWatchedIngressClassName
controllerServiceName / Host / Port // Higress 控制器地址
controllerJwtPolicy / controllerAccessToken
serviceListSupportRegistry // service 列表模式二选一(见 L05)
clusterDomainSuffix
读法:Builder 模式让"有一大堆可选参数"的对象构造变清晰:
.withKubeConfigPath(x).withControllerPort(y)....build()。这些字段的值来自 Day 02 SdkConfig 用 @Value 读入的部署配置。L05
两处"二选一"
SDK 里有两个重要的运行时分叉:
- 连接方式:
KubernetesClientService构造函数(:140-179)区分 in-cluster(Pod 内读 ServiceAccount token)vs kubeConfig(路径或内容字符串)。Day 08 详讲。 - service 列表模式:
HigressServiceProviderImpl.java:54-58按serviceListSupportRegistry选ServiceServiceImpl(走 Higress 控制器/debug/registryz)或ServiceServiceByApiServerImpl(直接列 K8s Service)。
别把"两种模式"理解成"两种存储"
有人以为 Higress 有"K8s 模式"和"单机模式"两套存储实现——其实不是。存储永远是"说 K8s API",所谓单机模式只是 Higress 内置了一个轻量 apiserver,Console 照样用 kubeConfig 连它。上面两处分叉只是"怎么连"和"从哪列服务"的差异,不是存储实现的差异。
L06
SDK 目录地图
constant/ KubernetesConstants(注解/标签常量), HigressConstants(默认值)
model/ 领域实体(Route/Service/ServiceSource/Domain/TlsCertificate/
WasmPlugin(Instance)/consumer/*/ai/*/mcp/*)
service/ 领域服务接口 + *Impl
service/kubernetes/ KubernetesClientService, KubernetesModelConverter,
KubernetesUtil, crd/(EnvoyFilter/McpBridge/WasmPlugin POJO)
service/ai/ AiRouteServiceImpl, LlmProviderServiceImpl, 各厂商 Handler
service/consumer/ ConsumerServiceImpl, KeyAuthCredentialHandler
service/mcp/ McpServerService...
exception/ BusinessException/NotFoundException/
ResourceConflictException/ValidationException
L07
异常体系
sdk/exception/ 定义四种业务异常,昨天在切面 getHttpStatus 里见过它们的 HTTP 映射:
ValidationException→ 400(入参不合法)ResourceConflictException→ 409(资源已存在/版本冲突)NotFoundException→ 502(上游依赖找不到)BusinessException→ 500(通用业务错误,常包装 K8sApiException)
读法:SDK 抛这些"语义化异常",console 的切面统一翻译成 HTTP 状态。SDK 不关心 HTTP——它只表达"发生了什么业务问题",怎么呈现给前端是 console 的事。又是一次干净的分层。
L08
今日小结 + 动手
🧠 今天你应该能回答
- SDK 为什么不依赖 Spring?这么做有什么好处?
- 门面模式在
HigressServiceProvider里怎么体现? - "手写依赖注入"是什么?setter 回填解决什么问题?
- 两处"二选一"分别是什么?为什么"两种模式"不是"两种存储"?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/sdk/src/main/java/com/alibaba/higress/sdk
sed -n '28,61p' service/HigressServiceProvider.java
sed -n '51,77p' service/HigressServiceProviderImpl.java
sed -n '31,61p' config/HigressServiceConfig.java
ls exception/ model/
明天预告 · Day 07:配置存储——为什么 SDK 的"存储层"是薄封装而非仓储接口,Ingress/ConfigMap/Secret/CRD 的 CRUD 长啥样,以及 console 的
ConfigService 和 SDK 存储的区别。