第二章 核心技术架构层
产品矩阵平台真正难的地方,不是把几个产品放进同一个代码仓,而是让每个产品都能保持自己的节奏,同时共享一套稳定、可治理、可演进的技术底座。
这一层要回答三个问题:
- 请求从用户进入系统后,经过哪些边界?
- 业务模块之间如何协作,又如何避免互相污染?
- 平台能力如何从同步接口,扩展到异步事件、缓存、权限、灰度和任务调度?
2.1 应用层:API Gateway、BFF 与 GraphQL
应用层是外部世界进入平台的第一道门。对产品矩阵而言,入口不会只有一个:微信小程序、Web 管理台、移动 App、开放 API、第三方 Webhook 都会同时存在。
推荐拆成三类入口:
| 入口 | 主要职责 | 典型场景 |
|---|---|---|
| API Gateway | 鉴权、限流、签名、路由、审计 | 所有外部请求统一入口 |
| BFF | 面向端侧聚合数据,减少前端拼装 | 管理台首页、移动端个人中心 |
| GraphQL | 复杂查询和多资源组合 | 低代码后台、开放查询接口 |
API Gateway 不应该承载业务逻辑。它更像机场安检:检查身份、行李和登机口,但不负责飞机怎么飞。真正的业务决策应下沉到服务层,否则网关会很快变成“第二个单体”。
一个可落地的请求链路如下:
sequenceDiagram
participant Client as Client
participant Gateway as API Gateway
participant BFF as BFF
participant Service as Service Layer
participant Repo as Repository
Client->>Gateway: Request + JWT + X-App-ID
Gateway->>Gateway: Auth / Rate Limit / Signature
Gateway->>BFF: Forward normalized request
BFF->>Service: Call domain services
Service->>Repo: Query / Command
Repo-->>Service: Domain data
Service-->>BFF: Business result
BFF-->>Client: View model
2.2 业务服务层:模块化 Service Layer
Service Layer 是平台业务能力的主战场。一个成熟平台至少要把服务分成三类:
| 服务类型 | 示例 | 设计重点 |
|---|---|---|
| 基础服务 | 用户、租户、认证、文件 | 稳定、强一致、低变更 |
| 业务服务 | 订单、内容、表单、任务 | 高内聚、边界清晰 |
| 平台服务 | 配置、审计、计费、通知 | 跨产品复用、规则可配置 |
Service 只暴露意图明确的方法,而不是把数据库表操作原样暴露出去。例如 CreateOrder 比 InsertOrder 更合适,因为创建订单可能包含库存检查、优惠计算、支付单生成、事件写入等步骤。
推荐的服务方法形态:
type OrderService interface {
CreateOrder(ctx context.Context, cmd CreateOrderCommand) (*OrderDTO, error)
CancelOrder(ctx context.Context, cmd CancelOrderCommand) error
}
这里的 Command 是业务输入,不是 HTTP 请求体的直接复用。这样做可以避免 Service 被某一个端的字段设计绑死。
2.3 多租户与多应用上下文
产品矩阵平台中,每一次请求都至少有三层身份:
| 上下文 | 含义 | 来源 |
|---|---|---|
| User | 当前登录用户 | JWT、Session、OAuth |
| Tenant | 企业、品牌、开发者主体 | 域名、AppID、JWT Claims |
| App | 具体产品或应用实例 | Header、路径、客户端配置 |
上下文必须在网关或中间件层被解析,然后通过 context.Context 进入 Service 和 Repository。不要在业务代码里反复读取 Header,因为那会让业务逻辑依赖 HTTP 环境,未来迁移到 Worker、CLI 或消息消费时会很痛苦。
2.4 数据访问层:GORM、Repository 与 Hook
Repository 的职责不是“包一层 ORM”,而是稳定数据访问契约。业务层不应该知道某个查询用了联表、索引、缓存还是读库。
推荐边界:
type UserRepository interface {
FindByID(ctx context.Context, id string) (*User, error)
FindByUnionID(ctx context.Context, tenantID string, unionID string) (*User, error)
Save(ctx context.Context, user *User) error
}
GORM Hook 适合处理横切能力:
| Hook 场景 | 建议做法 |
|---|---|
自动写入 tenant_id | 从 context.Context 读取租户 |
| 软删除 | 统一 deleted_at 策略 |
| 审计字段 | 自动写入 created_by、updated_by |
| 数据变更事件 | 谨慎使用,复杂事件优先放 Service |
Hook 不适合承载复杂业务规则。比如“支付成功后赠送会员权益”不应该藏在 AfterUpdate 里,否则调用方很难知道一次保存会触发哪些副作用。
2.5 异步与事件驱动架构
平台内的事件应分成两类:
| 类型 | 例子 | 要求 |
|---|---|---|
| 领域事件 | order.created、user.registered | 语义稳定,可被多个模块订阅 |
| 集成事件 | sms.send.requested、file.thumbnail.required | 面向外部系统或异步任务 |
最容易踩坑的是:数据库事务提交成功了,消息发送失败了。建议使用 Outbox Pattern,在业务事务内先写入 outbox_events 表,再由独立 Worker 投递到 Kafka、NATS 或 Redis Stream。
业务事务:写订单 + 写 outbox_events
后台任务:扫描未投递事件 → 发布消息 → 标记已投递
消费端:按 event_id 幂等处理
这样即使消息系统短暂不可用,平台也不会丢失关键事件。
2.6 缓存与分布式锁
缓存的第一原则是:先定义一致性要求,再选择技术方案。
| 数据类型 | 推荐策略 | 例子 |
|---|---|---|
| 读多写少 | Cache Aside + TTL | 商品详情、文章详情 |
| 强配置读取 | 本地缓存 + Redis 发布订阅 | Feature Flag |
| 高频计数 | Redis 原子操作 + 异步落库 | 点赞数、浏览量 |
| 临界资源 | 分布式锁 + 超时保护 | 库存扣减、账单生成 |
分布式锁必须有过期时间、唯一 token 和释放校验。不能简单地 DEL lock_key,因为可能误删别人刚拿到的锁。
2.7 接口安全与签名体系
开放平台接口要同时处理三件事:身份可信、内容未被篡改、请求不可重放。
一个实用的签名规则:
sign_string = method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body)
signature = HMAC-SHA256(app_secret, sign_string)
服务端校验顺序:
- 检查
app_id是否存在、是否启用; - 检查
timestamp是否在允许窗口内; - 检查
nonce是否已使用; - 重新计算签名并常量时间比较;
- 写入审计日志。
2.8 灰度发布与版本控制
产品矩阵会同时服务多个 App 和租户,不能把“发布代码”当成“发布功能”。Feature Toggle 是必需品。
| 灰度维度 | 示例 |
|---|---|
| 租户 | 只给某个企业开启新版报表 |
| App | 只给小程序端开启新结算页 |
| 用户 | 按用户 ID 尾号抽样 5% |
| 环境 | dev、staging、prod 分层 |
灰度配置要有版本号、变更人、发布时间和回滚记录。否则一次开关变更很难追踪事故来源。
2.9 分布式任务调度与工作流
平台任务通常分为定时任务、延迟任务、批处理任务和长流程任务。不要把它们都塞进一个 Cron。
| 任务类型 | 例子 | 推荐实现 |
|---|---|---|
| 定时任务 | 每日账单、数据归档 | Scheduler + 分布式锁 |
| 延迟任务 | 订单超时取消 | Redis ZSet / Delay Queue |
| 批处理 | 用户画像重算 | Worker Pool |
| 工作流 | 企业入驻审核 | 状态机 + 事件 |
工作流设计的关键是可恢复。每一步都应记录状态和输入输出,即使 Worker 重启,也能从最后一个成功节点继续执行。
2.10 本章落地清单
| 检查项 | 达标标准 |
|---|---|
| 入口层 | 网关只做通用控制,BFF 只做端侧聚合 |
| 服务层 | 服务接口表达业务意图,不泄露 HTTP 和 ORM |
| 数据层 | Repository 明确,Hook 只处理横切能力 |
| 事件层 | 关键事件使用 Outbox,消费者幂等 |
| 缓存层 | 每类缓存都有一致性策略和失效策略 |
| 安全层 | JWT、HMAC、OAuth2 各有边界 |
| 灰度层 | 配置变更可追踪、可回滚 |
| 调度层 | 任务可重试、可恢复、可观测 |
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。