58.5 技术选型流程

一句话

选型流程不是“比参数”,而是在预算与合规约束下,用可验证的证据把不确定性降到可接受,并留下退出路径。

先划边界:维度在 4.3,流程在本节

4.3 技术选型的评估维度 讲的是怎么打分:七个维度、一票否决、权重、POC 验证最贵的假设、可替换性。

本节讲的是怎么把一次选型从想法推进到合同与落地:谁发起、经过哪些门、每步产出什么、谁拍板、怎么退出。前者是“尺子”,后者是“流程”。

先破除四个误区

误区 现实
“Demo 好用就选它” Demo 是供应商挑的最好场景;你的场景是长尾、脏数据、异常流量
“只有一家候选” 单一供应商没有议价权,也没有对照;至少 3 个长名单,哪怕最后只剩 1 个
“POC 跑通就行” 没有假设与退出标准的 POC,只是“再演示一遍”;跑通不等于在你的规模下可用
“先决定,采购/法务后面补” 合同、数据处理协议、出口条款谈不下来,技术选型就是空中楼阁

七个阶段,每步有产出

阶段 做什么 负责人 时长(示例) 产出
① 问题与约束 要解决什么问题、NFR、预算、合规 发起人 + 技术 lead 1 周 一页问题说明
② 长名单 列 3–6 个候选(含“自建/不选”) 技术 lead 3–5 天 候选清单
③ 否决项 合规、许可证、维护性、数据可导出 安全/法务/架构 1–3 天 否决结论
④ 短名单 + 评分 按 4.3 的维度与权重打分 技术 lead + 评审组 1 周 打分表
⑤ POC 只验证最贵的 3 个假设 技术 lead 2–4 周 POC 报告
⑥ 决策会 + ADR 拍板、记录背景/备选/后果 决策者 1 天 ADR
⑦ 采购与落地 合同、SLA、迁移、回看 采购/法务/团队 2–6 周 合同 + 上线

总时长通常 6–12 周;超过 12 周还没决策,通常是“问题没定义清”或“没人敢拍板”,不是技术问题。

角色:谁负责什么

角色 职责 不该做
发起人 说清业务问题、预算、时间窗 指定技术方案
技术 lead 候选、评分、POC、迁移方案 绕过安全/法务
架构 与目标架构/路线图对齐(见 58.4 演进路线图) 替业务定优先级
安全/合规 威胁建模、数据流、合规结论 最后一天才出现
采购 商务谈判、合同、SLA 替技术做技术判断
法务 许可证、数据处理协议、退出条款 事后补签
财务 预算、三年 TCO 口径 只看首年报价
决策者 在信息充分时拍板并承担结果 既不决策也不授权

POC:先写“怎么算失败”

项 要求 反例
假设 ≤3 个,可证伪 “验证它好不好用”
指标 基线 + 目标 + 测量方法 “性能提升明显”
退出标准 达标/未达标分别怎么决策 没有预先定义
时间盒 2–4 周,超时即停 无限期“再试试”
成本上限 人力/资源/云费用上限 先花再说
数据 脱敏数据;禁止直接拿生产数据 直接连生产库

POC 报告只写三件事:哪些假设被验证、哪些被推翻、还有什么没验证。不要写成产品评测。

一个能跑的“选型门禁”

决策会之前先过门:材料不齐,不进会。这样能避免“会上才发现没做安全评审”。

package main

import (
	"fmt"
	"os"
)

type selection struct {
	name       string
	problem    bool // 问题与约束写清楚
	candidates int  // 进入短名单的候选数
	vetoDone   bool // 合规/许可证/维护性等否决项已过
	pocDone    bool
	pocChecks  int // POC 验证通过的假设数
	security   bool
	legal      bool
	exitPlan   bool // 数据导出/替换路径/合同退出条款
	adr        bool
	budget     bool
}

type gate struct {
	name, status, detail string
}

func check(s selection) ([]gate, bool) {
	var gs []gate
	add := func(name string, pass bool, detail string) {
		status := "PASS"
		if !pass {
			status = "BLOCK"
		}
		gs = append(gs, gate{name, status, detail})
	}

	add("问题定义", s.problem, "要解决什么问题、约束是什么")
	add("候选长名单", s.candidates >= 3, fmt.Sprintf("候选 %d 个(至少 3 个,避免单一供应商)", s.candidates))
	add("否决项", s.vetoDone, "合规、许可证、维护性、数据可导出")
	add("POC", s.pocDone && s.pocChecks >= 3, fmt.Sprintf("POC 完成=%v,验证假设 %d 个(至少 3)", s.pocDone, s.pocChecks))
	add("安全评审", s.security, "威胁建模/数据流/权限")
	add("法务与采购", s.legal, "合同、SLA、数据处理协议")
	add("退出计划", s.exitPlan, "数据导出、抽象层、替换成本")
	add("决策记录", s.adr, "ADR 写清背景、备选、决策、后果")
	add("预算", s.budget, "三年 TCO 与采购预算已批")

	ok := true
	for _, g := range gs {
		if g.status == "BLOCK" {
			ok = false
		}
	}
	return gs, ok
}

func run(s selection) bool {
	gs, ok := check(s)
	fmt.Printf("== 选型门禁:%s ==\n", s.name)
	for _, g := range gs {
		fmt.Printf("  %-6s %-10s %s\n", g.status, g.name, g.detail)
	}
	if ok {
		fmt.Println("  结论:可以进决策会\n")
	} else {
		fmt.Println("  结论:有 BLOCK 项,不能进决策会;补齐或明确接受风险(需书面记录)\n")
	}
	return ok
}

func main() {
	good := selection{
		name: "消息队列选型", problem: true, candidates: 4, vetoDone: true,
		pocDone: true, pocChecks: 4, security: true, legal: true,
		exitPlan: true, adr: true, budget: true,
	}
	bad := selection{
		name: "缓存中间件选型", problem: true, candidates: 1, vetoDone: false,
		pocDone: false, pocChecks: 0, security: false, legal: false,
		exitPlan: false, adr: false, budget: true,
	}

	okA := run(good)
	okB := run(bad)
	fmt.Printf("可进决策会:A=%v,B=%v\n", okA, okB)
	if !okA || !okB {
		os.Exit(1)
	}
}

本机运行结果(退出码 1):

== 选型门禁:消息队列选型 ==
  PASS   问题定义       要解决什么问题、约束是什么
  PASS   候选长名单      候选 4 个(至少 3 个,避免单一供应商)
  PASS   否决项        合规、许可证、维护性、数据可导出
  PASS   POC        POC 完成=true,验证假设 4 个(至少 3)
  PASS   安全评审       威胁建模/数据流/权限
  PASS   法务与采购      合同、SLA、数据处理协议
  PASS   退出计划       数据导出、抽象层、替换成本
  PASS   决策记录       ADR 写清背景、备选、决策、后果
  PASS   预算         三年 TCO 与采购预算已批
  结论:可以进决策会

== 选型门禁:缓存中间件选型 ==
  PASS   问题定义       要解决什么问题、约束是什么
  BLOCK  候选长名单      候选 1 个(至少 3 个,避免单一供应商)
  BLOCK  否决项        合规、许可证、维护性、数据可导出
  BLOCK  POC        POC 完成=false,验证假设 0 个(至少 3)
  BLOCK  安全评审       威胁建模/数据流/权限
  BLOCK  法务与采购      合同、SLA、数据处理协议
  BLOCK  退出计划       数据导出、抽象层、替换成本
  BLOCK  决策记录       ADR 写清背景、备选、决策、后果
  PASS   预算         三年 TCO 与采购预算已批
  结论:有 BLOCK 项,不能进决策会;补齐或明确接受风险(需书面记录)

可进决策会:A=true,B=false

门禁不是官僚:九项里任何一项缺失,决策会都会退化成“听供应商讲 PPT”。特别注意“退出计划”和“法务与采购”这两项——它们最容易被技术团队忽略,也最容易在两年后变成巨额成本。

锁定与退出:选型时就要想好怎么分手

维度 要确认的事 合同/技术上的保障
数据 能否全量导出?格式是否标准? 导出工具 + 定期演练
协议 是否标准协议(SQL/HTTP/S3/Kafka) 避免私有协议
抽象 业务代码是否依赖厂商 SDK 适配层/接口隔离
合同 退出条款、数据删除、迁移协助 写进合同,不靠口头
源码 是否托管/escrow 关键系统要求源码托管
价格 涨价机制、最低消费 锁定涨幅与期限

云厂商锁定与迁移的完整分析见 5.7 云厂商锁定与迁移;成本口径见 4.2 成本-收益分析。

选型落地后还要写清 “什么信号出现就换方案”(止损条件、执行人与复核日),见 4.7 技术风险与止损——没有止损条件的选型,等于把决定权交给了下一次事故。

决策与回看

决策会产出两样东西:

  1. ADR:背景、候选、评分、决策、后果、退出计划(见 58.1 ADR 实践)。
  2. 落地计划:迁移步骤、验收指标、回滚方案(重大变更走 58.3 架构评审)。

12 个月回看:当初的假设哪些成立?成本是否超支?故障是否在预期内?如果重来还会选它吗?把结论补进 ADR,作为下次选型的输入。

生产落地:指名 → 可抄 → 可验 → 可退

指名:选型报告用 Markdown/Confluence;评分表按 4.3 的维度;决策落 ADR;采购用公司既有流程(不要绕过)。Make/Buy/Reuse 的判断见 4.4 Make / Buy / Reuse 决策。

可抄(选型报告骨架):

# 消息队列选型报告

## 问题与约束
- 峰值 8k QPS;保留 7 天;P99 < 50ms;预算 30 万/年

## 候选与否决
| 候选 | 否决项 | 结论 |
|---|---|---|
| Kafka | 无 | 进入短名单 |
| RocketMQ | 团队无生产经验 | 保留,风险项 |
| 自建 | 人力不足 | 否决 |

## POC 结论
- 假设 1(8k QPS 下 P99 < 50ms):成立,实测 38ms
- 假设 2(积压 1 亿条可恢复):成立,耗时 22 分钟
- 假设 3(运维人力 < 0.5 人):未验证

## 决策
选 Kafka;代价:需要补监控与值班 runbook;退出计划:标准协议 + 双写迁移。

可验:本节门禁真跑过,输出如上;把它放在决策会邀请前,BLOCK 未清就不排会。人工验证:随机抽一次选型,检查是否有 ≥3 个候选、POC 是否有预设退出标准、合同是否有退出条款。

可退:选错了要能退。技术上留适配层与数据导出;合同上留退出条款与迁移协助;组织上留“12 个月回看”的机制。没有退出路径的选型,本质是一次不可逆赌博。

反模式

反模式 后果
Demo 驱动选型 选的是演示效果,不是生产可用性
单一候选/内定 没有对照与议价,风险无人兜底
POC 没有退出标准 跑通了也不知道该不该选
采购/法务后置 合同谈不下来,技术方案作废
只看首年报价 三年 TCO 与人力成本被低估
没有退出计划 被锁定后迁移成本极高
决策会没有 ADR 半年后没人记得为什么选它
选完不复盘 同样的坑下次再踩

自检

Q1 4.3 已经讲了评估维度,为什么还需要选型流程?

▸ 答案:维度解决“怎么比”,流程解决“谁来比、按什么顺序、每一步产出什么、谁拍板、怎么退出”。没有流程,打分表会被跳过、否决项会被忽略、采购与法务会拖到最后一刻。流程把“技术判断”变成“组织可执行的决策”。

Q2 为什么要求至少 3 个候选,哪怕最后只认真评估 1 个?

▸ 答案:候选的作用是提供对照与议价,并逼团队说清“为什么不选另外两个”。只有一个候选时,评审无法判断是“最优”还是“没得选”,采购也没有议价空间。3 个是下限,不是形式。

Q3 POC 的退出标准应该怎么定?

▸ 答案:在 POC 开始前写死:验证哪 3 个假设、每个假设的指标与测量方法、达标/未达标分别怎么决策、时间盒与成本上限。没有退出标准的 POC 会无限延长,最后变成“再试试”或“感觉还行”。

Q4 为什么“退出计划”要在选型时就定,而不是等以后再说?

▸ 答案:锁定成本随时间上升:数据越多、依赖越深、合同越长,迁移越贵。选型时还有议价权,可以要求标准协议、数据导出工具、退出条款;上线两年后再谈,供应商没有动力配合。退出计划是选型的一部分,不是运维的补救。

Q5 12 个月回看应该看什么?

▸ 答案:看四件事:① 当初的容量/性能假设是否成立;② 三年 TCO 是否超支、超在哪;③ 故障是否在预期内、恢复是否达标;④ 如果重来是否还选它。结论补进 ADR,作为下一次选型的输入,而不是停留在口头。

关联