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、谁依赖谁,完全不用管。
门面模式:外部只握一个入口,内部零件全藏后面 调用方 console / 脚本 Provider(门面) create() + 一堆 getter RouteService WasmPluginService AiRouteService … KubernetesClientService
图注:调用方 → 门面 → 各 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 里有两个重要的运行时分叉:

  1. 连接方式KubernetesClientService 构造函数(:140-179)区分 in-cluster(Pod 内读 ServiceAccount token)vs kubeConfig(路径或内容字符串)。Day 08 详讲。
  2. service 列表模式HigressServiceProviderImpl.java:54-58serviceListSupportRegistryServiceServiceImpl(走 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(通用业务错误,常包装 K8s ApiException
读法: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 存储的区别。
← Day 05 Day 07 · 配置存储 →