做大型系统架构设计的这些年,我经常被问到同一个问题:TOGAF、DDD、SaaS这三样到底怎么选,能不能一起用?问的人多了我才发现,很多人一开始就把这三个概念放在了错误的比较维度上。TOGAF是企业架构方法论,DDD是软件建模方法论,SaaS是一种软件交付形态,它们解决的是完全不同层面的问题。在一次技术评审会上,业务方说要做SaaS平台,技术负责人说要用DDD建模,架构师说必须按TOGAF规范来,三方各说各话,会议开了两个小时没有任何结论。这篇文章就是把我在大型系统架构设计中的实践经验整理出来,讲清楚这三者如何从分工到协同,真正落进同一个系统里,给正在做技术决策的架构师和技术Leader一个可参考的路径。
1. 开讲之前,先把TOGAF、DDD、SaaS三者定位理清楚
1.1 三个概念其实不在同一个维度上
很多团队架构评审吵得不可开交,本质原因是拿A的标准去评价B。TOGAF是The Open Group制定的企业架构框架,关注的是整个企业的战略、业务、数据、应用和技术五个层面怎么规划。DDD是Eric Evans提出的领域驱动设计,关注的是复杂业务领域怎么拆解、怎么建模。SaaS则是Software as a Service,是一种软件交付模式,关注一套系统如何同时服务多个客户并按订阅收费。
三者的关系可以用一个盖楼的类比来理解:TOGAF是城市规划和建筑设计规范,决定了这块地建什么、功能分区怎么排、各栋楼之间的消防通道怎么留;DDD是室内设计方法,决定了一个楼层内部怎么划分房间、动线怎么走、每个房间的用途是什么;SaaS则是这栋楼建成后的运营模式,同一栋楼里可以入驻很多家公司,各家用各家的门禁,但水电和电梯是共享的。
从这个角度看,它们不是竞争关系,而是不同层面的协作关系。搞不清这个,后面所有架构决策都会变形。
1.2 我理解的三层协作关系
我自己在项目里用一张三层模型来定位它们:
| 层级 | 方法论 | 回答的问题 | 主要产出 |
|---|---|---|---|
| 战略与治理层 | TOGAF | 企业要做什么,架构怎么治理 | 业务能力地图、架构原则、分层架构蓝图 |
| 业务建模层 | DDD | 业务复杂度怎么拆解、模块边界怎么划 | 限界上下文、领域模型、聚合设计 |
| 交付形态层 | SaaS | 系统以什么形态交付给多个客户 | 租户隔离方案、计费配额机制、多租户部署形态 |
每次架构设计启动前,我会先把这三层模型摆出来。如果讨论的是技术选型、分库分表、部署方案,那是SaaS形态层的问题;如果讨论的是订单状态流转、聚合根设计、事件溯源,那是DDD建模层的问题;如果讨论的是业务能力规划、系统间集成边界、架构治理流程,那是TOGAF治理层的问题。把问题归类到正确的层级之后,很多争论自然就消失了。
2. TOGAF做骨架:从企业战略到技术架构的逐层翻译
2.1 ADM方法里最值钱的不是走完流程,而是分层拆解
TOGAF的核心是ADM(Architecture Development Method),一套从架构愿景到架构治理的迭代流程。网上对ADM的解读很多,但我实操下来,它最有价值的不是那八个阶段是否都走了一遍,而是强制你在做技术架构之前,先把业务架构、数据架构、应用架构分开来想清楚。
我在做大型系统架构时,会重点做四层翻译:
- 业务架构层:把企业战略转成业务能力地图。比如一个SaaS平台,战略目标是“服务中小企业客户成功”,那业务能力可能包括客户获取、订阅管理、用量计量、客户成功干预、收入确认等。这些业务能力不依赖任何技术系统,纯粹描述“企业能做什么”。
- 数据架构层:识别企业级数据实体和数据Owner。租户、用户、订单、资源配额、计费记录,每一个关键数据都有明确的归属方和使用方,这一步在SaaS场景下特别重要,因为多租户直接改变了数据的所有权和隔离边界。
- 应用架构层:把支撑业务能力的应用系统或服务划分出来。认证服务、租户管理服务、计费服务、控制台服务、数据服务,每个应用支撑哪几个业务能力,应用之间怎么集成,在这一层定清楚。
- 技术架构层:确定部署形态、中间件选型、计算存储网络方案。这层解决的是“用什么技术实现在前面三层定义的需求”。
这四层翻译的顺序不能乱。我见过太多团队跳过业务架构直接讨论“用Kubernetes还是用ECS”,最后系统做出来跟业务战略对不上,返工成本极高。
2.2 架构原则要能指导每天的日常决策
TOGAF里容易被低估的是架构原则。很多团队把架构原则当成评审PPT里的装饰品,什么“系统要高性能”“系统要可扩展”,写完就没人看了。但架构原则如果写得好,是可以直接指导开发团队日常决策的。
我常用的几个原则示例:
- 租户数据不越界:所有数据访问必须携带租户上下文,禁止跨租户默认查询。
- 计费数据不可篡改:计费事件只允许追加,不允许更新和删除。
- 核心能力自主可控,非核心能力优先复用商业化组件。
这些原则每一条都是可检验的。每次技术评审,拿这三条原则去卡方案,很多争议根本不需要上升到架构评审会层面就能解决。原则不在多,在于能用。
2.3 TOGAF落地的克制:不要做成文档工程
这是TOGAF落地最容易翻车的地方。很多团队一上TOGAF,就照着框架清单把所有交付物都做一遍,最后产出一堆几百页的Word文档和PPT,没人看,也没人维护。架构治理变成了文档维护。
我的做法是只保留三类核心产出物:第一是业务能力地图,用一页纸画清楚企业要做什么;第二是架构原则,控制在十条以内,每条可检验;第三是差距分析表,明确从当前状态到目标状态要补齐哪些关键能力。其他TOGAF推荐的artifact按需产出,不强求。做架构设计是为了帮团队做对决策,不是为了证明方法论用得完整。
3. DDD做血肉:限界上下文与领域模型的关键实操
3.1 战略设计比战术设计重要得多
DDD分战略设计和战术设计两部分。战略设计关心的是怎么把大业务拆成一个个限界上下文,以及这些上下文之间怎么协作;战术设计关心的是在单个上下文内部怎么设计聚合、实体、值对象、领域服务和领域事件。
在大型系统架构中,战略设计的价值远大于战术设计。因为大型系统的问题从来不是“某个聚合怎么写”,而是“边界到底怎么划”。边界划对了,内部代码怎么组织都能运行;边界划错了,后面所有迭代都在为边界错误买单。
我识别限界上下文时用的是三步法:
- 画业务流程泳道图,找到业务语言发生变化的地方。业务语言一变,就是一个新的限界上下文。比如“客户”在销售语境下可能强调“潜在客户”,在计费语境下强调“付费方”,在客服语境下强调“工单归属人”,不同语境下对客户的操作和规则完全不同。
- 看数据一致性边界。哪些数据必须在同一个事务边界内强一致,哪些允许最终一致。强一致边界内通常是一个上下文。
- 看团队协作结构。如果两个团队只需要通过接口交互,不需要共享内部数据结构,那就可以切分为两个上下文。康威定律在这里很好用。
3.2 聚合粒度的“手感”怎么练
聚合是DDD战术设计里最容易走极端的部分。有的团队把聚合设计得特别大,一个聚合根挂了几十个实体,实际上是拿微服务的壳做单体的事;有的团队把聚合拆得特别碎,一个订单一个聚合,一个订单项又是一个聚合,结果业务规则散落得到处都是。
我判断聚合粒度的标准是:一次业务操作涉及的一组强一致性数据。也就是说,当你要保证“要么全部成功,要么全部失败”的数据范围就是聚合边界。比如创建订单时,订单头、订单项、订单状态流转记录需要在一个事务里,那它们就是一个聚合;但创建订单和扣减库存通常可以走最终一致,那就拆分到两个聚合,通过领域事件协作。
这个标准看起来简单,实际判断时经常需要跟业务专家反复确认。我建议不要追求一次设计到位。聚合边界是会随业务发展演进的,先把当前业务明确要求的强一致边界做对,其余边界留到业务有明确诉求时再调整。
3.3 防腐层与开放主机服务:新老系统共存的必修课
大型系统几乎不会从零开始。存量系统、老模型、外部依赖,都是新架构必须面对的现实。很多DDD项目死在这里,因为模型建得很漂亮,但一接入老系统就发现对方的数据结构、业务语义跟新模型完全对不上。
防腐层(ACL)解决的就是这个问题。在老系统与你的新模型之间加一层翻译逻辑,不让老系统的模型污染新模型。老系统返回的是一个“订单状态=1”的字段,你的新模型需要的是“OrderStatus.PENDING_PAYMENT”,翻译动作就发生在防腐层里,而不是让你的领域层去理解老系统的编码。
开放主机服务(OHS)则是主动对外发布稳定的接口协议,把内部领域模型的演进隔离在协议之下。当你需要把某个限界上下文的能力开放给其他团队或外部系统时,先定义一套稳定的API契约,内部怎么改都不影响外部使用者。
防腐层和开放主机服务是DDD在大型系统落地时最容易被忽略、但价值最高的战术工具。没有它们,领域模型在真实系统里根本活不过三个迭代。
3.4 领域事件:跨上下文协作的主干通道
DDD的领域事件在多租户系统里有特别强的应用价值。比如“租户创建完成”“套餐变更生效”“配额即将超限”,这些都是业务世界中真实发生的、其他上下文关心的关键节点。
我的实践是把领域事件作为跨上下文协作的主干通道。租户上下文创建租户后发布TenantCreated事件;计费上下文订阅这个事件后初始化计费账户;配额上下文订阅后设置默认配额。这种事件驱动的协作方式让上下文之间的耦合从同步调用变成了异步协同,局部故障不会扩散到全局。
落到技术实现上,我一般先用数据库发件箱模式(Transactional Outbox)保证领域事件与业务数据的一致发布,再从Outbox表把事件投递到消息队列。这一步看起来多了一个环节,但避免了分布式事务带来的复杂度爆炸,是我在项目中反复验证过的稳妥方案。
4. SaaS做形态:多租户隔离与商业化约束的架构应对
4.1 三种租户隔离模式,选错一个后面都是债
SaaS架构绕不开的核心问题是多租户隔离。常见的有三种模式,每种都有明确的成本与隔离权衡:
- 共享应用、共享数据库(Pool模式):所有租户共用一套代码库、一个数据库实例,通过租户ID区分数据。优点是成本最低、运维最简单,缺点是隔离性最弱,一个租户的数据量暴增会影响所有租户,而且数据恢复、备份恢复都是全量操作。
- 共享应用、独立Schema或独立库(Bridge模式):代码共用,但每个租户的数据落在独立的Schema或独立的数据库里。隔离性和可恢复性比Pool模式好很多,运维上需要一定的自动化能力来支撑Schema的创建、迁移和备份,成本中等偏上。
- 独立应用实例、独立基础设施(Silo模式):每个租户有自己独立的一套环境和资源。隔离性最强,适合合规要求高、数据量大的大客户,但成本线性增长,运维复杂度非常高。
我建议按客户价值和合规要求做组合策略,而不是全平台统一一种模式。初创SaaS或面向中小客户的场景,从Bridge模式起步通常是比较稳的;遇到大客户签私有化或独享实例的需求,再走Silo模式,这是商业上常见的演进路径。
4.2 租户维度的贯穿性设计:从数据库到DDD模型
多租户架构真正的挑战不是选一种隔离模式,而是让“租户维度”贯穿所有设计决策。在DDD模型层面,租户不是一个普通的实体,它更像一个横切维度。
我在实际项目里是这样落位的:领域实体的聚合根上不显式散落一堆租户ID逻辑,而是通过基础框架层统一注入租户上下文。数据访问层在拿到租户上下文后自动拼接租户过滤条件,业务代码里不写WHERE tenant_id。这样做的好处是防止业务开发者开发时漏掉租户条件,造成数据越权。
这里给一段数据访问层租户过滤的示意逻辑,是我常用的实现思路:
// 基于MyBatis-Plus的多租户插件思想,但生产上建议自己封装 @Component public class TenantLineInnerInterceptor implements InnerInterceptor { @Override public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 从上下文获取当前租户ID,禁止使用全局静态变量 Long tenantId = TenantContextHolder.getTenantId(); if (tenantId == null) { throw new TenantMissingException("租户上下文缺失,禁止执行无租户条件的查询"); } return true; } @Override public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { // 在这里改写SQL,自动为查询和写入语句拼接 tenant_id 条件 // 实现细节略,核心是对 BoundSql 做 AST 解析后注入租户条件 } }实现细节在不同ORM框架里不一样,但核心原则是一致的:租户隔离必须由框架层保证,而不是依赖每个开发者的业务代码自觉。
4.3 套餐计量与配额控制:SaaS商业化绕不开的硬骨头
很多SaaS平台做到中后期才发现,计费计量比业务功能本身复杂得多。套餐费用策略不是简单地“标准版多少钱、专业版多少钱”,而是要落到一系列可计量的资源维度上:用户数、项目数、存储量、API调用次数、团队成员数、高级功能开关。
我从架构角度把这一块拆成四个子系统:
- 事件采集:业务系统在关键行为发生时上报计量事件,比如文件上传、成员邀请、API调用。这里要注意事件格式的统一和采集的异步化,不能因为计量逻辑阻塞主业务流程。
- 用量聚合:把原始事件按照租户、时间维度、资源维度聚合成用量数据。一般是分钟级或小时级的定时聚合,聚合结果进入用量表。
- 计费计算:根据套餐定义和用量数据计算费用。套餐变更、超标提醒、账单生成都在这个环节。
- 配额控制:在用量接近或超过套餐限额时触发控制动作,比如限制上传、降级服务、发送提醒。
配额控制这块有一个容易被忽视的点:配额检查与业务操作之间的一致性。如果业务操作先执行成功,配额检查才发现超限,多租户场景下很容易出现“先用后罚”的边界模糊问题。我的做法是预占配额,业务成功后正式扣减,业务失败则释放预占,这样能把超限误差控制在最小范围。
5. 一个完整落地案例:三者如何在同一套系统里协同
5.1 案例背景:搭建一套面向中小团队的SaaS协作平台
用一个我实际经历过的项目来串前面讲的方法论。需求背景是给中小团队做一套项目协作与文件管理平台,目标客户是几十人到几百人的团队,按团队规模和高级功能收费。这个系统同时涉及多租户、复杂业务规则、商业化计费,非常适合用它来演示TOGAF、DDD、SaaS如何协同。
很多人拿到这种需求会直接开始建表、写接口。但按我的经验,先做架构设计决策,后面至少能少走两三个月的弯路。
5.2 战略层:用TOGAF做业务能力地图和架构分层
第一步是用TOGAF梳理业务能力。这是整个项目最关键的一步,很多团队在这步偷懒导致后面返工。我建立的项目协作平台业务能力地图包括:
- 用户与组织管理:账号注册、成员邀请、组织架构维护。
- 项目管理:项目创建、任务分配、进度跟踪、项目归档。
- 文件协作:文件上传、版本管理、在线预览、共享权限。
- 订阅与计费:套餐选择、订单支付、用量计量、账单管理。
- 运营分析:租户活跃度、功能使用率、健康度指标。
在数据架构层,关键数据实体包括租户(Tenant)、用户(User)、成员关系(Membership)、项目(Project)、任务(Task)、文件(File)、订阅计划(Plan)、用量记录(UsageRecord)、订单(Order)。
应用架构层我规划了认证服务、租户管理服务、项目服务、文件服务、计费服务、数据服务六个应用。技术架构层采用Spring Boot + PostgreSQL + Redis + 消息队列的常见组合,初期部署为模块化单体,保证服务边界清晰,后期按需求拆分独立服务。
5.3 业务层:用DDD划分子域与限界上下文
在TOGAF已经划好的应用架构基础上,DDD的业务建模把每个应用内部的边界梳理得更细。
我识别出的限界上下文有:
切分时最重要的判断是身份上下文与租户上下文一定要分开。很多团队把用户和租户混在同一个域里,导致“用户属于哪个租户”这个本该由成员关系决定的问题变成了用户本身的属性,后续做多租户权限和计费时就会很别扭。
在项目管理上下文里,项目(Project)是聚合根,任务(Task)是聚合的一部分。一个项目下挂多个任务,任务状态流转规则封装在项目管理上下文内部。文件协作上下文通过领域事件监听任务相关的文件引用。计费上下文监听成员邀请和文件上传产生的用量事件,做配额预扣和计算。这个模型跑起来后,各上下文之间的依赖特别清晰。
5.4 形态层:多租户隔离与部署形态的取舍
该项目我选了Bridge模式,即共享应用、每个租户独立Schema。原因是目标客户是中小团队,单个租户的数据量不会特别大,业务上又需要比较强的数据隔离和可恢复性。独立Schema方案下,备份恢复某个租户非常方便,出了问题不会殃及池鱼,对SaaS平台的稳定性口碑很重要。
同时我在技术框架层做了租户识别的统一处理。控制台切租户、API网关识别租户、数据访问层自动注入租户条件,登录用户始终带着租户上下文,数据操作从入口到出口都被租户维度约束。这个基础能力花费了一周时间,但之后所有业务模块的交付效率都因此受益。
部署形态上,我是从模块化单体起步的。Java的Spring Boot工程包含身份、租户、项目、文件、计费五个模块,模块之间只通过接口调用,不允许跨模块数据表访问。这样既保证了开发初期的交付效率,又给后续按限界上下文拆分微服务留好了边界。
5.5 从领域事件到API:跨上下文协作的细节落地
在这个系统里,跨上下文协作完全走领域事件通道。租户管理上下文创建租户时发布TenantCreated事件,计费上下文订阅后创建计费账户和默认配额;文件服务检测到存储量接近套餐限额时发布QuotaWarning事件,通知服务向管理员推送告警;计费上下文在套餐到期未续费时发布SubscriptionSuspended事件,项目服务订阅后把项目切换到只读模式。
API层我坚持用OHS的思路做统一适配。每个限界上下文对外的API契约独立维护,接口版本管理跟内部实现解耦。对外API按业务语义命名,比如POST /v1/tenants、GET /v1/projects、POST /v1/files/upload,而不是暴露内部的领域模型结构。这样外部调用方感知到的是一套稳定的业务协议,内部模型怎么演进都不会破坏外部兼容性。
6. 我踩过的三个坑:每一条都是真金白银换来的
6.1 TOGAF文档做成了文档工程,团队没人执行
我第一次把TOGAF完整引入项目时犯过一个典型错误:按照TOGAF的交付物清单,产出了架构愿景、业务架构文档、信息系统架构文档、技术架构文档、架构路线图等一套东西。文档做完分发出去,开发团队基本不看,评审会上也走形式。原因很简单,文档里写的都是抽象名词,跟开发每天写的代码没有直接关联。
后来我把TOGAF的产出物压缩成三样:一页纸的业务能力地图,十条以内的架构原则,一张差距分析表。架构原则跟代码规范绑定,比如“租户数据不越界”变成了数据访问层强制拼接租户条件的代码约束;“计费数据不可篡改”变成了数据库权限控制加API层禁止更新操作。原则落到代码层面后,团队执行度立刻上来了。TOGAF在大型系统里的价值是“治理”,但治理的前提是让人看得懂、执行得了,不是文档多。
6.2 DDD建模陷入“完美主义”,聚合改了三轮也没定稿
做DDD最怕建模强迫症。我有个阶段反复纠结任务和项目的聚合边界,第三轮建模时明显感觉团队情绪不对了——开发不知道按哪个版本写代码,业务专家也不确定业务规则到底该怎么描述。
后来复盘,问题出在我试图把未来可能的业务变化都提前设计进模型。DDD的建模应该基于当前业务事实和明确预期,不是预测所有未来。我调整策略,先做“薄建模”:把当前业务明确要求的强一致边界、聚合根和关键业务规则定下来,其他设计决策推迟到业务有真实需求时再做。模型不是一步到位的,业务持续演进,模型就要持续重构。想通这一点后,建模周期从三周压缩到三天,团队执行质量反而上来了。
6.3 “租户ID出逃”问题:一个被反复忽视的安全漏洞
多租户系统最隐蔽的坑是租户ID出逃。租户ID本身不会自己跑出去,但当某个SQL漏写了租户条件、某个缓存Key没带租户维度、某个后台任务没区分租户时,出逃就发生了。最危险的是那些不直接可见的运维操作,比如定时任务批量处理数据、大数据分析任务扫描全量数据,一个没带租户条件的后台任务就可能把A租户的数据覆盖到B租户头上。
这块我的防守策略是三层校验。第一层是数据访问层的租户拦截器,SQL执行前自动补充租户条件;第二层是应用层的租户上下文校验,所有领域服务的入口检查当前租户是否合法;第三层是运维层的审计机制,对不带租户条件的批量操作单独审批和记录。三层都在,基本能把出逃概率降到极低。
6.4 三套方法论“满配”时互相打架,最终靠优先级排序解围
最实际的冲突发生在微服务边界划分上。按DDD的限界上下文,某些服务边界可能很顺,但按SaaS的部署和扩缩容需求来看,可能希望把计费服务拆成独立部署单元,因为计费流量波动大、还要独立扩容。
我的解法是确立优先级:TOGAF定企业级边界,DDD定业务模型边界,SaaS定部署与隔离边界。业务模型边界尽量对齐限界上下文,但部署边界可以根据SaaS的运营需求灵活调整。比如计费上下文在DDD里是一个限界上下文,在部署上完全可以是多个服务实例;文件协作的存储处理逻辑和业务逻辑耦合不深,但可以分开扩容。方法论是工具,不是枷锁。任何方法论冲突时,回到业务价值和系统稳定性来评估,优先级排序自然就清晰了。
做完整套设计之后再回看,TOGAF、DDD、SaaS本质上是从三个不同角度回答了同一个问题:怎么让一个复杂系统既满足企业战略、又扛得住业务复杂度、还能支撑商业化规模运营。它不是三条互相排斥的路线,而是三个必须同时成立的约束条件。我自己在实际项目中反复验证下来的体会是:遇到大型系统时,先用TOGAF把治理框架搭起来,用DDD把业务边界划清楚,最后用SaaS的视角把所有设计决策过一遍租户维度和商业化维度,这个顺序基本不会出大问题。