软件设计之道-“田”字模型

151 阅读17分钟

软件设计之道-“田”字模型

开篇:《“老王烧烤”的数字化转型》

“兄弟们,大家好。今天我们来聊一个可能会改变我们工作习惯的新模型。在开始讲枯燥的理论之前,我先给大家讲个故事,关于咱们公司楼下那家‘老王烧烤’。”


第一部分:老王的传统手艺(过去的我们)

“老王烤得一手好羊肉串,以前生意全靠他一个人。客人来了,冲他喊:‘王哥,来一手肥点的,多辣!’ 老王心里记下,转身就从冰箱里拿肉,现穿现烤,最后吆喝一嗓子:‘您的串好嘞!’,亲自端上去。

这个过程,简单直接,但问题很大:

  1. 全凭脑子记: 人一多,谁点了啥、要不要葱花香菜,根本记不住,老上错菜。
  2. 效率极低: 他一个人又当点单员、又当厨师、又当传菜员,忙得脚不沾地,一晚上也接待不了几桌。
  3. 无法规模化: 老王病了或者想开分店,这套手艺根本复制不了。

这像不像我们过去的某些项目?

  • 需求全靠产品经理口头传达。
  • 一个高手程序员从头写到尾,又是前端又是后台又是数据库。
  • 代码耦合严重,只有他自己能维护。
  • 项目无法复用,再来一个类似的需求,还得重头再写一遍。

第二部分:老王的数字化危机(我们遇到的痛点)

“后来老王上了‘美团’,生意爆火,订单蜂拥而至。问题也更大了:

  • 输入不标准: 有客人备注‘微辣’,有写‘一点点辣’,有写‘辣度一级’。老王懵了,这‘一点点’到底是多少?
  • 处理不过来: 订单打印出来贴了一墙,老王根本看不过来,经常烤糊或者漏单。
  • 输出会出错: 外卖小哥挤在门口问:‘订单尾号6789是谁的?’,店员手忙脚乱地找,经常拿错。

这像不像我们系统间的混乱交互?

  • 上游系统传过来的数据格式千奇百怪,我们要写一大堆if...else来适配。
  • 系统内部逻辑复杂,一量上来就性能瓶颈,频繁宕机。
  • 输出的接口、文档混乱,调用方抱怨连连,联调成本极高。

第三部分:老王的智慧转型(“田”字模型的精髓)

“老王痛定思痛,花了点时间做了一个革命性的改革

flowchart TD
    A[顾客下单] --> B[统一输入<br>标准点单码]
    B --> C[标准化处理<br>中央厨房]
    C --> D[统一输出<br>标准化出餐]
    
    subgraph A [第一横:输入标准化]
        B
    end

    subgraph B [第二横:处理专业化]
        C
    end

    subgraph C [第三横:输出标准化]
        D
    end

    B --> C
    C --> D
  1. 【定义输入 - 第一横】:他搞了个标准化点单码。每个菜品都有一个唯一码,辣度固定为【不辣、微辣、中辣、特辣】四档。客人只能这么选。从源头上保证了输入的统一和清晰。

  2. 【优化处理 - 第二横】:他在后院搭了个中央厨房。前厅店员只负责接单,把标准订单传给后厨。后厨分工明确:有人专门穿串,有人专门掌火,有人专门刷酱。各司其职,专业高效

  3. 【明确输出 - 第三横】:他设立了统一的出餐口。每个烤好的套餐都用一个标有订单号的盘子装好,放在出餐台上。外卖小哥来了,直接报号取餐,又快又准。

“改革之后,老王的生意做到了这条街第一。”


第四部分:从烧烤到代码(我们的启示)

“兄弟们,我们写代码、做系统,和老王开烧烤店,本质是一模一样的!”

  • 那个 ‘标准化点单码’,就是我们的输入规范接口契约。我们必须先和上游定义清楚,他们到底‘喂’什么数据给我们。
  • 那个 ‘中央厨房’专业分工,就是我们的应用内部设计。我们要把自己的系统拆分成一个个职责单一的模块(比如订单服务、库存服务、支付服务),而不是一个什么都干的‘大泥球’。
  • 那个 ‘统一的出餐口’订单号,就是我们的输出结果。我们给前端、给其他系统提供的数据和接口,必须标准、清晰、稳定。

“如果我们不学老王,而是在美团订单涌来时,还在那手忙脚乱地靠脑子记‘一点点辣’,那结局只能是系统崩溃,全员加班,客户投诉。”

“所以,我们今天要介绍的这套 ‘田’字模型,其实就是把我们代码世界的‘中央厨房’给建起来。”

  • 第一横:输入 -> 搞清楚 ‘谁给我喂数据?’ (定义好你的点单码)
  • 第二横:处理 -> 想清楚 ‘我拿到数据后要干什么?’ (设计好你的中央厨房和流水线)
  • 第三横:输出 -> 明确 ‘我干完活要交出什么?’ (准备好你的出餐口)

“从今天起,我希望大家在动手写代码前,都像老王设计他的中央厨房一样,先花时间做好设计。先定义规矩,再开始干活。这样,我们才能从‘手工作坊’式的开发,转变为‘现代化工厂’式的生产,最终交付稳定、高效、可扩展的系统!”

“好,接下来,我们看看具体该怎么操作……”

一、【功能名称】功能边界定义表

1.1 功能概述

  • (用一两句话描述这个功能是干什么的)

1.2 输入 (Input) - “谁给我喂数据?”

指引: 列出所有触发此功能执行或为此功能提供数据的来源。思考:数据从哪来?是什么格式?怎么传给我?

1.2.1 上游调用者/数据来源
来源类型来源方(哪个应用/用户)触发方式(API调用/消息事件/定时任务)协议与数据格式(HTTP/Event, JSON/?)核心数据字段说明
例如:内部应用前端Web页面HTTP POST API调用application/json{ "productId": 123, "quantity": 2 }
例如:消息队列订单服务监听 order.created 事件JSON{ "orderId": "O001", "userId": "U001" }
...............
1.2.2 必要的前置条件与校验规则
  • 权限校验: 调用者需要具备什么角色/权限?
  • 数据校验: 输入数据需要满足哪些业务规则?(如:库存是否充足?)
  • 幂等性要求: 是否需要防止重复提交?Token是什么?

1.3 输出 (Output) - “我干完活要交出什么?”

指引: 列出这个功能执行后所产生的所有结果和副作用。思考:我要返回什么?要通知谁?要改变什么状态?

1.3.1 直接响应
输出类型消费者(返回给谁)输出方式数据格式核心数据字段说明
同步响应前端Web页面HTTP Responseapplication/json{ "orderId": "O001", "status": "created" }
...............
1.3.2 异步输出与副作用
输出类型消费者(通知给谁)输出方式数据格式核心数据字段说明
领域事件积分服务、消息推送服务发布 OrderCreatedEvent 消息JSON{ "orderId": "O001", "userId": "U001" }
数据库变更-写入 orders-生成订单数据
...............

1.4 处理 (Process) - “我拿到数据后要干什么?”

指引: 在厘清输入输出后,基于它们来设计核心业务逻辑。这会让你思路更清晰。

1.4.1 核心业务流程
flowchart LR
    A[接收输入请求] --> B{校验权限与数据}
    B -- 校验失败 --> C[返回错误信息]
    B -- 校验成功 --> D[执行核心业务逻辑-创建订单,扣减库存]
    D --> E[持久化数据到数据库]
    E --> F[发布订单创建事件]
    F --> G[返回成功响应]
1.4.2 关键领域模型与服务
  • 主要参与的模型: Order (订单), Product (商品)
  • 核心服务/方法: OrderService.createOrder()

1.5 讲清楚

  • 产品经理: ________ 我已确认上述输入、输出及业务逻辑符合需求定义。
  • 测试工程师: ________ 我已基于此文档开始设计测试用例,确认覆盖所有输入输出场景。
  • 技术负责人/架构师: ________ 设计合理,同意进入开发阶段。
  • 开发工程师: ________进入开发

二、核心理念:像搭积木一样构建应用

首先,告诉开发团队,我们要改变“接到需求就建表写CRUD”的习惯。每一个应用或功能模块都应该被看作一个有明确边界、有输入输出、可复用的“积木”(或称为“组件”、“服务”)。

这个“积木”的内部运作,必须遵循“”字模型:

flowchart TD
    subgraph SecondHorizontal [第二横:我们的应用/服务-处理]
        direction LR
        Process[内部业务逻辑<br>与数据处理]
    end

    Input[第一横:输入<br>上游应用/数据/事件] --> Process
    Process --> Output[第三横:输出<br>页面/接口/事件/数据]

    subgraph Roles [三竖角色-贯穿始终]
        direction TB
        R1[产品-定义做什么]
        R2[技术-设计怎么做]
        R3[用户/测试-验证好不好用]
    end

    Input -.-> Roles
    Process -.-> Roles
    Output -.-> Roles

架构师的指令:

“兄弟们,以后做任何一个新功能,先别敲代码。第一步,先把它框起来,问自己三个问题:

  1. 谁给我喂数据? (Input)
  2. 我拿到数据后要干什么? (Process)
  3. 我干完活要交出什么? (Output)

把这三个问题和产品、测试聊清楚,形成文档,我们再开始设计。”


三、开发流程:用清单驱动设计

接下来,为团队引入“清单文化”,将“田”字模型的抽象思维转化为具体的开发任务和设计文档。这个流程及其产出物如下图所示:

flowchart LR
    A[需求评审<br>PRD/原型] --> B[第一步:创建功能场景清单]
    B --> C[第二步: 细化输入,处理,输出清单]
    C --> D[第三步: 推导出数据库与集成设计]
    D --> E[第四步: 架构师评审清单]
    E -- 通过 --> F[编码实现]
    E -- 驳回 --> B

    subgraph B [第一步:创建功能场景清单]
        direction LR
        B1[梳理所有场景<br>明确每个场景的I-P-O]
    end

    subgraph C [第二步: 细化输入,处理,输出清单]
        direction LR
        C1[输入清单]
        C2[处理清单]
        C3[输出清单]
    end

    subgraph D [第三步: 推导出数据库与集成设计]
        direction LR
        D1[数据库设计清单]
        D2[集成清单<br>接口,事件,菜单]
    end

架构师的指令:

“我们的开发流程要增加一个设计环节。每个人都要负责填写这几张表:

1. 功能场景清单 (I-P-O清单):

场景编号场景名称输入 (Input)处理 (Process)输出 (Output)
C001用户下单商品ID、用户ID、收货地址1. 校验库存<br>2. 计算价格<br>3. 创建订单订单ID、支付链接
...............

2. 输入/处理/输出详细清单: 对每个场景的I、P、O进行细化。 3. 数据库设计清单: 表中的每个字段都必须能在上述清单中找到来源和目的。 4. 集成清单: 明确需要暴露哪些接口、监听哪些事件。

我会评审这些清单,通过之后才能开始写代码。这是我们的‘开工许可证’。”


四、设计目标:打造“可复用积木”,而非“一次性烟囱”

教导团队如何识别和设计“可复用积木”。关键在于分析“输入”的多样性,并在“处理”层做好抽象

flowchart TD
    subgraph A [不可复用的烟囱式设计-避免]
        A1[App 1] --> B1[定制化服务A<br>仅理解App1的数据]
        B1 --> C1[输出]
    end

    subgraph B [可复用的积木式设计-追求]
        direction TB
        D1[App 1] --> E
        D2[App 2] --> E
        D3[App 3] --> E

        subgraph E [通用服务-我们的应用]
            E1[通用输入适配层<br>参数配置/规则模板/数据转换]
            E2[核心业务处理层<br>保持稳定]
        end

        E --> F[通用输出层<br>接口/事件/页面]
        F --> G[其他应用]
    end

架构师的指令:

“我们要做的不是一个只能接一种数据、服务一个应用的‘烟囱’。我们要做的是一个万能适配器

比如设计‘证书服务’,不要只想着从‘培训系统’接数据。要把‘输入’设计得足够通用:

  • 用‘证书模板’来定义不同类型的证书。
  • 用‘参数配置’来适配不同来源的数据字段。
  • 用‘流程编排’来满足不同客户的不同审核流程。

这样,将来‘荣誉系统’、‘考核系统’想发证书,直接配置一下就能接入,不需要我们二次开发。这就是我们工作的价值所在!”


五、作为架构师,你要告诉开发

  1. 转变思维: 从“实现功能”转变为“设计积木”。每个功能都要先定义清其I-P-O边界。
  2. 规范流程: 推行“清单驱动开发”,将设计过程文档化、可视化,并将其作为编码的前置条件。
  3. 明确目标: 以“可配置”、“可复用”为最高追求之一,通过抽象输入、通用化处理,将系统打造成平台型产品,而非项目型定制。
  4. 质量把关: 用“九性原则”去评审清单和代码,确保性能、安全、可扩展等非功能需求得到满足。

六、落地实施四件套:流程、模板、评审、度量

我们将通过四个具体的抓手来确保开发按照“田”字模型进行设计。

6.1 抓手一:定义强制性的设计阶段流程

将“设计阶段”正式纳入开发流程,并设置为编码的前置关卡。可以使用类似如下的流程图来明确告知团队:

flowchart TD
    A[需求评审会] --> B{是否新功能/新应用?}
    B -- 是 --> C[启动-田字模型设计阶段]
    B -- 否<br>简单功能/bug修复 --> G[走简化流程或直接开发]

    subgraph C [设计阶段核心任务]
        C1[任务1: 撰写<功能场景清单>]
        C2[任务2: 撰写<输入输出清单>]
        C3[任务3: 撰写<领域模型清单>]
        C4[任务4: 产出<架构设计说明书>]
    end

    C -- 设计文档完成后 --> D[召开-设计评审会]
    D -- 评审通过 --> E[架构师在OA任务/JIRA上签字]
    E --> F[开发团队凭签字<br>开始编码]
    D -- 评审不通过 --> C

告诉开发:

“兄弟们,新流程来了。以后所有新需求,在PRD评审后,必须先在JIRA上完成【设计阶段】的任务,产出设计文档并通过评审,这个JIRA任务的状态才能变为【开发中】。这是硬性规定,没有例外。”


6.2 抓手二:提供标准化模板

创建模板,让开发“填空”,引导他们按“田”字模型思考。这是最重要的落地工具。

模板示例:《XXX功能/应用架构设计说明书》

6.2.1 功能场景清单 (I-P-O)
  • 说明: 拆解PRD,列出所有主要功能点,并用一句话说清每个功能的输入、处理和输出。
场景编号场景名称输入 (Input)处理 (Process)输出 (Output)
C001用户提交订单商品ID、SKU、数量、用户ID、地址1. 校验库存<br>2. 计算价格(含优惠券)<br>3. 创建订单数据1. 订单创建成功页面<br>2. 创建订单事件
...............
6.2.2 输入清单
  • 说明: 详细定义每个输入的来源、格式和约束。
6.2.2.1 内部输入(来自其他应用)
输入编号对应场景来源应用接口/事件名数据格式关键字段说明
I001C001商品中心GET /api/product/{id}JSONstock: integer
I002C001优惠券中心POST /api/coupon/validateJSONvalid: boolean, discount: number
6.2.2.2 外部输入(来自用户/第三方)
输入编号对应场景输入方式数据格式校验规则
E001C001HTTP POSTJSONaddress字段不能为空
6.2.3 处理清单(核心业务逻辑)
  • 说明: 对“处理”部分进行详细设计,这是领域建模的核心。
6.2.3.1 领域模型设计
模型名职责核心属性是否根实体
Order订单聚合根,负责订单生命周期orderId, userId, totalAmount, status
OrderItem订单项productId, quantity, price
6.2.3.2 关键业务流程设计

image.png

6.2.4 输出清单
  • 说明: 定义所有输出,明确消费者是谁。
6.2.4.1 接口输出
输出编号对应场景接口路径消费者数据格式
O001C001POST /v1/orders前端JSON
6.2.4.2 事件输出
输出编号事件名称对应场景消费者目的
EV001OrderCreatedEventC001支付中心、积分中心触发后续流程
6.2.5 数据库设计
  • 说明: 这里的表设计必须源自上面的领域模型和清单。
表名存储内容与场景编号关联主要字段
order订单主数据C001id, user_id, amount, status
order_item订单商品数据C001order_id, product_id, quantity
6.2.6 非功能性设计(九性原则考量)
原则中文释义核心问题设计策略/考量点
Performance性能系统响应快慢?吞吐量如何?响应时间、吞吐量、资源利用率、缓存、异步、池化、CDN、数据库优化
Reliability可靠性系统出故障的频率高吗?平均无故障时间(MTBF)、错误处理、容错、冗余、自动恢复
Availability可用性系统在多长时间内可供使用?故障时间占比(如99.99%)、冗余、故障转移、负载均衡、优雅降级
Security安全性系统能否防止恶意攻击和未授权访问?认证、授权、审计、加密、防注入、防篡改、漏洞管理
Modifiability可修改性系统容易修改吗?修改成本高吗?模块化、解耦、高内聚低耦合、接口抽象、微服务架构
Portability可移植性系统能轻易换一个环境运行吗?消除对OS、平台、数据库的依赖,使用容器化技术(Docker)
Reusability可复用性系统的部分功能能轻易被其他项目使用吗?组件化、服务化、清晰的接口设计、通用库、设计模式
Integrability可集成性系统能容易地与其它系统对接吗?标准化接口(RESTful API、GraphQL)、通用数据格式(JSON)、提供SDK
Testability可测试性系统容易测试吗?单元测试、依赖注入、 mocking、日志、健康检查接口
如何在实际设计中应用这九性?

在你的《设计说明书》中,“非功能性设计”部分就应该围绕这九性来展开。以下是如何将它们具体化的示例:

  • 1. 性能 (Performance):
    • 指标: 创建订单接口P99 < 200ms。商品列表查询接口支持 1000 QPS。
    • 设计: 使用Redis缓存商品信息。对数据库查询建立索引。异步处理非核心逻辑(如发送通知)。
  • 2. 可靠性 (Reliability) & 3. 可用性 (Availability):
    • 指标: 系统整体可用性达到99.95%(全年故障时间不超过4.4小时)。订单数据零丢失。
    • 设计: 数据库采用主从复制。服务部署至少2个实例,并配置负载均衡。关键操作(如扣库存)必须具备幂等性。
  • 4. 安全性 (Security):
    • 指标: 无高危安全漏洞。
    • 设计: 所有接口需通过网关校验JWT令牌。实施RBAC权限模型。对用户输入进行严格校验和过滤,防止SQL注入和XSS攻击。敏感信息(如密码)加密存储。
  • 5. 可修改性 (Modifiability) & 6. 可复用性 (Reusability):
    • 指标: 新增一种支付方式,开发周期不超过3人/日。
    • 设计: 采用策略模式设计支付模块。将订单核心领域与支付、物流等外部能力解耦,定义清晰的接口。力争将“支付能力”提炼为可复用的公共组件。
  • 7. 可移植性 (Portability):
    • 设计: 使用Docker容器化部署,消除对环境依赖。所有配置外部化,可通过配置中心管理。
  • 8. 可集成性 (Integrability):
    • 设计: 提供清晰的RESTful API文档(使用Swagger/OpenAPI)。为前端提供便捷的SDK。定义明确的事件契约(如OrderCreatedEvent),方便其他系统订阅。
  • 9. 可测试性 (Testability):
    • 设计: 代码遵循依赖倒置原则,便于注入Mock进行单元测试。提供/health端点供健康检查。生成充足的日志,便于集成测试和线上问题排查。

6.3 抓手三:设立正式的设计评审会

流程和模板有了,就需要一个仪式来审视成果。

  • 会议名称: “架构设计评审会” 或 “技术方案评审会”。
  • 参会人: 架构师、该项目的开发人员、主测、感兴趣的其他开发。
  • 评审依据: 就是上面要求提交的《架构设计说明书》。
  • 评审核心问题:
    1. 完整性: 三横(I-P-O)是否都覆盖了?清单是否齐全?
    2. 清晰性: 设计是否清晰?其他人是否能看懂?
    3. 可复用性(工具化): 输入是否抽象得足够通用?是不是又是“烟囱式”设计?能否通过配置应对未来类似需求?
    4. 可行性: 技术实现上有无难点?依赖的服务是否可靠?
    5. 质量: 是否考虑了“九性原则”(如性能、可靠性、可用性、安全性、可修改性、功能性、可变性、互操作性、易用性)?

告诉开发:

“设计文档写完后,预约评审会。在会上,你需要对着你的文档给大家讲清楚你的设计。我们会重点问上面这几个问题。评审不通过,打回去重新设计,直到通过为止。


6.4 抓手四:度量与持续改进

如何知道这个方法论是否成功?需要度量。

  1. 量化指标:

    • 设计阶段平均耗时: 从PRD评审到设计评审通过的时间。初期会变长,后期应稳定并缩短。
    • 返工率: 因设计缺陷导致的代码返工比例。这个指标应该下降。
    • 需求吞吐量: 理论上,因为前期设计得好,后期编码调试更顺,整体吞吐量应上升。
  2. 质性反馈:

    • 定期(如每季度)收集开发和产品的反馈:“你觉得新的设计流程有帮助吗?有什么痛点?”
    • 收集“因为前期设计好而避免了后期大坑”的成功案例,在团队内部分享,增强大家的认同感。