我们面临一个典型的内部平台困境:一个统一的仪表盘需要聚合来自不同业务团队的“小组件”(Widget)。每个团队都希望独立开发、测试和部署他们的小组件,而平台团队又不希望每次有小组件更新就重新部署整个前端和后端应用。这不仅是技术问题,更是团队协作效率问题。
最初的构想是微前端,但引入 Webpack Module Federation 或 single-spa 这样的框架,随之而来的是复杂的构建流水线、环境配置和运维负担,对于我们这个规模的团队来说,投入产出比并不理想。我们需要一个更轻量、更符合成本效益的方案。Serverless 成了自然而然的选择,而技术栈的选型则是在务实和性能之间寻求一个平衡点。
最终的架构决定是:使用 Azure Functions 作为无服务器计算平台,Ktor (一个基于 Kotlin Coroutines 的异步框架) 作为后端逻辑核心,并引入 Rollup 作为每个前端小组件的标准化打包工具。这个组合看起来有些非主流,但它精准地解决了我们的核心痛痛点:隔离、独立部署和低成本运维。
架构蓝图与核心契约
在动手之前,最关键的一步是定义清晰的系统边界和插件契约。整个系统的核心思想是,每个UI小组件都是一个独立的“插件”,它由两部分组成:
- 后端逻辑 (Kotlin/Ktor): 一个实现了特定接口的 JAR 包,负责数据获取和业务逻辑。
- 前端视图 (JavaScript/Svelte/Vue): 一些UI代码,通过 Rollup 打包成一个标准的 ES Module (ESM) 文件。
请求的生命周期如下:
sequenceDiagram participant Client as 浏览器 participant AF as Azure Functions (HTTP Trigger) participant Ktor as Ktor 引擎 participant Registry as 插件注册中心 participant Plugin as 插件JAR (动态加载) participant Storage as Azure Blob Storage (CDN) Client->>+AF: GET /api/widget/{widgetId} AF->>+Ktor: 路由请求 Ktor->>+Registry: lookup(widgetId) Registry-->>-Ktor: 返回插件实例 Ktor->>+Plugin: fetchData() Plugin-->>-Ktor: 返回业务数据 (Map) Ktor->>Storage: 获取插件对应的JS模块CDN URL Storage-->>Ktor: 返回 URL (e.g., cdn.domain/plugin-a/main.js) Ktor-->>-AF: 构造JSON响应 { data: {...}, scriptUrl: "..." } AF-->>-Client: 返回JSON Client->>+Storage: