4. 平台架构与领域建模
平台工程最容易失败的方式,不是技术选型错误,而是把平台当成一个“大系统”来建设:所有能力堆在一个仓库、一套服务、一个团队里,最终变成难以演进的巨石。要避免这一点,必须同时回答三个问题:平台在纵向上如何分层、在横向上如何按领域切分、在演进上如何保持可扩展与模块化。本节围绕这三条主线展开。
一、平台分层架构:把“谁依赖谁”固定下来
平台分层解决的是依赖方向问题。一个可维护的平台通常分为四层,自下而上依次为:基础设施层(计算、存储、网络、集群)、平台能力层(调度、流水线、制品库、配置中心、可观测性)、平台服务层(面向研发场景的自助式 API 与门户)、开发者体验层(CLI、IDE 插件、脚手架、文档)。
分层的核心约束只有一条:上层可以依赖下层,下层不得反向依赖上层。例如流水线能力可以调用集群调度接口,但集群调度不能反过来调用某个团队的流水线插件。这条约束一旦被打破,平台就会退化成互相纠缠的网状结构。实践中可以用依赖检查工具在 CI 中强制校验,例如在构建阶段扫描模块导入关系,发现下层模块引用了上层包名即判定失败。
分层还决定了变更的影响半径。基础设施层升级通常影响全局,需要灰度与回滚预案;平台能力层升级影响所有接入团队,需要兼容性窗口;平台服务层与体验层可以按团队独立发布。把变更影响半径写进架构决策记录(ADR),是让分层真正落地的关键动作。
二、领域驱动设计在平台中的应用
平台本身也是一个业务系统,它的“业务”就是研发与运维流程。因此领域驱动设计(DDD)同样适用,但重点不在业务实体,而在限界上下文与通用语言。
第一步是识别限界上下文。以典型平台为例,可以划分出:制品管理上下文、环境与配置上下文、流水线编排上下文、可观测性上下文、权限与租户上下文。每个上下文拥有自己的数据模型与术语。例如“环境”在配置上下文中指一组配置集合,在流水线上下文中指一次部署的目标,两者同名不同义,必须显式区分,否则接口会持续产生歧义。
第二步是定义上下文映射关系。常见模式有三种:共享内核(如统一的租户 ID 规范)、客户-供应商(流水线依赖制品库提供的稳定 API)、防腐层(对外部云厂商 API 做适配封装,避免其模型污染平台内部模型)。防腐层尤其重要:云厂商的实例规格、网络模型变化频繁,若直接透传到平台服务层,每次云侧变更都会引发平台连锁修改。
第三步是让通用语言进入代码与文档。例如团队统一使用“应用(Application)—环境(Environment)—发布单(Release)”三个核心概念,那么 API 路径、数据库表名、CLI 命令都应保持一致,而不是出现 app/service/project 混用。语言一致是模块边界清晰的前提。
三、可扩展性与模块化设计原则
可扩展性不是“预留很多接口”,而是让新增能力时不需要修改已有代码。实现这一点依赖三条具体原则。
第一,高内聚。同一限界上下文的数据与逻辑必须放在同一模块内。反例是多个模块直接读写同一张数据库表,一旦表结构变更,所有模块都要改。正确做法是每个上下文独占自己的存储,其他上下文通过 API 获取数据。
第二,低耦合。模块间只通过稳定契约通信。契约应版本化,例如 /api/v1/releases,新增字段保持向后兼容,删除字段必须走废弃流程。平台能力层对上层暴露的接口应尽量窄,避免把内部实现细节(如具体调度器的队列名)暴露出去。
第三,可替换。每个能力模块定义接口,实现可插拔。例如流水线引擎接口定义 createRun、getStatus、cancel 三个方法,底层可以接 Jenkins、Tekton 或自研引擎。这样当团队决定更换引擎时,只需新增一个实现,而不改动平台服务层。
下面给出一个可参考的模块契约示例,用于约束流水线引擎的接入方式:
interface PipelineEngine {
createRun(spec: RunSpec): Promise<RunId>;
getStatus(runId: RunId): Promise<RunStatus>;
cancel(runId: RunId): Promise<void>;
}
// RunSpec 只包含平台通用字段,不暴露引擎私有参数
type RunSpec = {
appId: string;
env: string;
artifact: { repo: string; version: string };
stages: string[];
};
这段契约的关键在于:RunSpec 只使用平台通用语言(appId、env、artifact、stages),不出现任何引擎特有字段。任何引擎实现都必须把自身参数映射到这套通用模型,从而保证上层服务层不受引擎更换影响。
四、把三者串起来:一个演进检查清单
分层、领域建模、模块化不是三件独立的事,而是同一套约束在不同视角下的表达。落地时可以按以下顺序检查:
- 依赖方向是否单向?用 CI 依赖检查工具验证,发现反向依赖立即阻断合并。
- 每个限界上下文是否有独立的数据模型与术语表?同名不同义的概念是否已显式区分?
- 模块间是否只通过版本化 API 通信?是否存在跨模块直连数据库的情况?
- 核心能力是否定义了接口契约?更换实现时是否需要修改调用方代码?
- 每次架构变更是否记录了 ADR,说明影响半径与回滚方式?
这五条检查不需要一次性全部满足,但应作为平台演进过程中的持续约束。平台架构的价值不在于一次设计得多完美,而在于每次新增能力时,依赖方向、领域边界和模块契约都不被破坏。