Day 03 / 共 20 天 · 第 1 周 后端架构

后端分层:三层怎么分工

今天建立后端的"分层地图":Controller(薄)→ Service(业务)→ SDK/K8s(存储)。理解了这三层的边界,后面每读一个功能就知道"该去哪一层找"。

📍 你在整门课的位置(第 1 周 后端架构)
D01 全景 D02 启动 D03 分层 D04 REST D05 横切 D06 SDK D07 存储 D08 客户端 D09 模型 D10 流转
💡 一个类比看懂三层分工(把后端想成一家餐厅) Controller = 服务员:只负责接单、看你点得对不对(校验),从不进厨房,一句"好嘞"就把单子递进去。Service = 厨房:真正做菜的地方,一道菜常要多个步骤(建路由要同时写 Ingress + 鉴权资源,就是"一菜多工序"的编排)。SDK / K8s = 仓库:食材都在这儿存取。三层各管一段、互不越界——服务员换话术不影响后厨,后厨换做法不影响仓库。这就是"分层 = 关注点分离"。
L01

三层总览

L1 Controllerconsole/controller/):@RestController,只做"接收 HTTP + 校验入参 + 委派",不含业务逻辑。
L2 Service:分两类——console 自身业务(登录/仪表盘/系统配置)+ SDK 资源 Service(路由/服务/插件/AI,真正操作 K8s)。
L3 SDK/K8ssdk/service/kubernetes/):KubernetesClientService 直接读写 K8s;KubernetesModelConverter 做"领域模型 ↔ K8s 对象"转换。
为什么要分这么多层? 每层只关心自己那点事,改起来互不影响。控制器改 API 形状不用碰 K8s 逻辑;SDK 改 K8s 写法不用碰 HTTP。而且 SDK 是纯 Java 库(不依赖 Spring),别的程序也能拿去复用。分层 = 关注点分离 = 可维护 + 可复用。
后端三层:一份请求从上往下穿 L1 Controller(服务员)· 薄 接 HTTP + 校验入参 + 委派,不含业务 L2 Service(厨房)· 编排 一个操作可牵连多个 K8s 资源 L3 SDK / K8s(仓库)· 存储 ClientService 读写 + Converter 翻译 请求 K8s
图注:越往下越"靠近数据";每层只跟上下相邻层打交道,改一层不牵动别层。
L02

Controller 层:薄

统一风格:@RestController + @RequestMapping("/v1/...") + @Validated,字段用 @Resource 注入 SDK service,返回 ResponseEntity<Response<T>>。以 RoutesController.java:45-52 为例:

@RestController
@RequestMapping("/v1/routes")
@Validated
public class RoutesController {
    @Resource private RouteService routeService;   // 昨天 SdkConfig 注册的 Bean
    // ...
}
读法:控制器只做校验 + 委派:检查名字非空、名字合法,然后 routeService.add(route) 一句话交给下一层。真正的业务在 Service 里。Day 04 会把全部控制器和端点列一遍。
L03

Service 层:业务

Service 是"编排中枢"。比如"创建路由"不是简单写一个 Ingress——RouteServiceImpl.addRouteServiceImpl.java:121-136)会:① 把 Route 转成 Ingress;② 调 K8s 创建;③ 还要 writeAuthConfigResources 写鉴权相关资源。这种"一个操作牵连多个 K8s 资源"的编排,正是 Service 层的价值。

典型编排例子:一条 AI 路由(Day 11)会同时展开成 Ingress + model-router 插件 + model-mapper 插件 + ai-statistics 插件 +(可选)fallback 路由 + EnvoyFilter——全在 AiRouteServiceImpl 一个方法里编排完成。
📝 举个例子:一句"建路由"在 Service 里其实是几步 前端只发了一次 POST /v1/routes(带鉴权),但 RouteServiceImpl.add 在厨房里做了三道工序:
route2Ingress 把 Route 翻成 Ingress → ② createIngress 写进 K8s → ③ writeAuthConfigResources 再写一份鉴权资源。一次点击 → 多个 K8s 对象,这种"打包编排"就是 Service 层存在的理由。(完整链路 Day 10 逐行追。)
L04

SDK/K8s 存储层

最底层是 sdk/service/kubernetes/KubernetesClientService.java(813 行)——它把每类 K8s 资源的增删改查封成方法:Ingress、ConfigMap、Secret、以及 CRD(McpBridge/WasmPlugin/EnvoyFilter)。旁边的 KubernetesModelConverter.java(2102 行)负责把领域对象翻译成 K8s 对象。

"存储层"就是这两个类 别的项目里"存储层"通常是数据库 DAO。Higress Console 没有数据库——它的"存储"就是 Kubernetes API Server,所有数据都以 K8s 资源的形式存在集群里。KubernetesClientService 就是它的"DAO",KubernetesModelConverter 就是它的"ORM 映射"。第 2 周整周都在拆这两个类。
L05

两类 Service(重要区分)

  1. console 自身业务 serviceconsole/service/,是 Spring @Service):SessionService(登录)、DashboardService(仪表盘)、ConfigServiceImpl(系统配置)。
  2. SDK 资源 servicesdk/service/不是 Spring 组件):RouteServiceImplServiceServiceImplWasmPluginServiceImplAiRouteServiceImpl…由 HigressServiceProvider 手动装配,再在 SdkConfig 注册为 Bean。
读法:第一类是"给人用的控制台功能"(登录、看仪表盘);第二类是"操作网关配置"。区分它们的关键:前者带 @Service 注解被 Spring 自动扫描,后者是 SDK 里手写 new 出来的。
L06

包结构地图

# console 模块
HigressConsoleApplication.java  启动类
WebMvcInitializer.java          静态资源 + SPA 回退
config/    SdkConfig, SwaggerConfig
controller/ 17+ 控制器 + ai/ + mcp/ + dto/ + util/ + exception/
service/   ConfigService, DashboardService, SessionService, SystemService
aop/       ApiStandardizationAspect(切面), AllowAnonymous
client/grafana/  Grafana 客户端

# sdk 模块
config/    HigressServiceConfig
service/   HigressServiceProvider(Impl), RouteServiceImpl, ...
service/kubernetes/  KubernetesClientService, KubernetesModelConverter, crd/
service/ai/          AiRouteServiceImpl, LlmProviderServiceImpl, 各厂商 Handler
service/consumer/    ConsumerServiceImpl, KeyAuthCredentialHandler
service/mcp/         McpServerService...
model/               Route, Service, Domain, ai/*, consumer/*, mcp/*
把这张地图收藏——后面 17 天几乎都在这几个目录里游走。
L07

无数据库!(本项目最反直觉的一点)

连控制台自己的配置都存在 K8s 里 你可能以为管理后台总得有个 MySQL 存点东西吧?没有。连 console 自己的设置都存在一个 K8s ConfigMap(默认名 higress-console,见 ConfigServiceImpl.java:42-43)里。为什么?因为 K8s 本身就是个带持久化、带 watch、带一致性的"数据库"——Console 复用它,就不用自己维护数据库、备份、迁移。这也让 Console 天然"无状态",重启不丢数据、可以随便扩副本。

👶 小白:没数据库,那我建的路由重启后不就没了吗?

👨‍🏫 老师:不会。数据根本不在 Console 进程里,而是写进了 K8s 这个"带持久化的档案室"。Console 只是往档案室存/取,自己是无状态的——重启、扩到 3 个副本,都读同一份档案,数据一致也不丢。这正是"用 K8s 当数据库"白捡的好处。

⚠️ 常见误解:以为管理后台必然配一个 MySQL。本项目连自己的设置都塞在一个 ConfigMap 里(ConfigServiceImpl.java:42-43)——全课不会出现任何数据库。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 后端三层分别是什么?各自的边界?
  • 两类 Service 怎么区分?谁被 Spring 扫描、谁是手写 new?
  • "存储层"在这个项目里指哪两个类?
  • 为什么说 Console 没有数据库?它的数据存哪?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend
ls console/src/main/java/com/alibaba/higress/console/controller/
ls sdk/src/main/java/com/alibaba/higress/sdk/service/
ls sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/
sed -n '45,52p' console/src/main/java/com/alibaba/higress/console/controller/RoutesController.java
明天预告 · Day 04REST API 层——把全部控制器和端点列一张总表,然后精读最典型的 RoutesController 的 CRUD,看校验、状态码策略、统一响应怎么做。
← Day 02 Day 04 · REST API →