如何用履约建模法设计埋点平台数据结构
一句话先讲清楚
如果我要设计的不是某个业务系统里的埋点,而是埋点平台本身,我不会先从 click 或 pageview 开始建,而会先用履约建模法把平台自己的承诺关系找出来,也就是谁定义事件、谁提交数据、平台怎么验收、最后又怎么把数据交付给下游。
这句话是核心。
30 秒版本
我会把埋点平台看成一个数据履约平台,而不是简单的日志收集系统。因为它真正要解决的,不是“收到了多少事件”,而是事件有没有定义、有没有按约定提交、平台有没有确认接收、数据质量合不合格、下游能不能稳定消费。所以我会先按履约建模法拆上下文,再去落事件定义、采集请求、验收结果和数据交付这些核心结构。
1 分钟版本
如果让我用履约建模法来设计埋点平台,我不会先问“埋哪些按钮”,而会先问平台里到底有哪些履约关系。因为埋点平台本身不是业务系统,它更像一个中间平台,核心是管理数据的定义、接入、验收和消费。
所以我会先拆四个上下文。第一是事件定义上下文,也就是谁定义了什么事件、字段和口径;第二是采集接入上下文,也就是 SDK 或客户端把什么数据提交进来;第三是数据验收上下文,也就是平台怎么校验、接收、拒收或者隔离这些数据;第四是数据消费上下文,也就是报表、分析、告警这些下游怎么稳定消费平台交付的数据。
这样拆完以后,数据结构就不会只是一个 event 表,而会变成一条完整的数据履约链路,比如事件定义合同、上报请求凭证、事件事实、校验确认、交付记录。这样的平台结构会更适合做治理、审计和追责。
我在面试里会怎么口语化讲这个建模过程
我通常会按下面这个顺序讲,而不是一上来就甩表结构。
第一步:先澄清我建模的对象到底是什么
我先区分了一下,我要建模的不是业务系统里的“订单埋点”或者“任务埋点”,而是埋点平台本身。也就是说,我关心的是这个平台怎么定义事件、怎么接收数据、怎么做质量控制、怎么给下游交付数据。对象一旦变了,核心结构就不应该再从按钮点击开始,而应该从平台自己的履约关系开始。
这一步其实是在找合同上下文。
第二步:先找平台里有哪些“合同关系”
按履约建模法,我会先看平台里到底谁和谁之间存在承诺关系。比如业务方和平台之间,承诺的是“我按约定给你提交事件”;平台和业务方之间,承诺的是“我按照定义帮你接收、校验和保存”;平台和下游分析系统之间,承诺的是“我给你交付可消费的数据能力”。
所以我会先拆出四个上下文:
- 事件定义上下文
- 采集接入上下文
- 数据验收上下文
- 数据消费上下文
第三步:从履约凭证而不是从字段开始
我不会先说 event 表有哪些字段,而会先找每个上下文里的凭证。因为履约建模法强调的是证据链。对于埋点平台来说,真正重要的不是“平台里有条数据”,而是“这条数据是按什么定义来的、谁提交的、平台是否确认接收、最后有没有被交付出去”。
所以我会先抽这几类核心凭证:
- 事件定义凭证:
EventSpec - 采集请求凭证:
IngestionRequest - 事件事实凭证:
EventFact - 验收确认凭证:
ValidationResult - 数据交付凭证:
DeliveryRecord
第四步:再回到数据项,问每个字段从哪里来
到这一步我才会开始落字段。这里我会用履约建模法里一个很重要的思路,就是每个关键数据项都要能回答来源。来源只可能有三种:用户输入、前序凭证提供、算法计算。
比如 eventKey 来自事件定义合同,requestId 来自采集请求凭证,status 可能来自平台校验结果,rawHash 是平台算法计算出来的去重依据。这样建出来的结构不是拍脑袋补字段,而是能顺着证据链往回追。
第五步:最后再补角色和标的物
等凭证流有了,我再去补参与方。比如谁是 Producer,也就是 SDK / App / 服务端;谁是 Platform;谁是 Consumer;谁是治理方。然后再补标的物,比如 EventSpec、EventBatch、EventFact、Dataset。
这样最后得到的不是孤零零的一张事件表,而是一整套围绕“定义 - 提交 - 验收 - 交付”的平台模型。
我最终会落下来的核心数据结构
1. 事件定义合同 EventSpec
type EventSpec = {
specId: string
eventKey: string
eventName: string
category: string
ownerTeam: string
ownerUser?: string
version: number
status: draft | reviewing | active | deprecated
fields: EventField[]
contextFields: string[]
triggers?: string[]
createdAt: number
updatedAt: number
}
type EventField = {
fieldKey: string
fieldName: string
type: string | number | boolean | object | array
required: boolean
description?: string
enumValues?: string[]
}这个结构解决的是“埋什么”和“按什么口径埋”。
2. 采集请求凭证 IngestionRequest
type IngestionRequest = {
requestId: string
appId: string
sdkVersion: string
tenantId?: string
source: web | mobile | server | miniapp
batchId: string
eventCount: number
receivedAt: number
transport: beacon | xhr | fetch | kafka
traceId?: string
payloadRef?: string
}这个结构解决的是“谁在什么时候提交了一批什么数据”。
3. 事件事实 EventFact
type EventFact = {
factId: string
requestId: string
specId?: string
eventKey: string
eventTime: number
ingestTime: number
userId?: string
anonymousId?: string
sessionId?: string
deviceId?: string
page?: string
payload: Record<string, unknown>
rawHash: string
}这个结构保存真正发生过的事件,但它必须能回挂到定义合同和采集请求。
4. 验收确认 ValidationResult
type ValidationResult = {
validationId: string
factId: string
specId?: string
status: accepted | rejected | quarantined
errors: ValidationIssue[]
checkedAt: number
}
type ValidationIssue = {
code: string
level: warn | error
fieldKey?: string
message: string
}这个结构解决的是“平台是否确认接收,以及为什么拒收”。
5. 数据交付记录 DeliveryRecord
type DeliveryRecord = {
deliveryId: string
consumerId: string
datasetId: string
mode: dashboard | export | stream | alert
windowStart?: number
windowEnd?: number
deliveredAt: number
status: success | partial | failed
}这个结构解决的是“平台最后把什么数据交付给了谁”。
如果面试官追问“为什么不是一个大 event 表就够了”
因为一个大 event 表只能回答“发生了什么”,但回答不了“这条数据按哪个定义来的、是不是合法提交、为什么被拒收、最后有没有被交付给下游”。埋点平台如果只建 event 表,本质上还是日志收集系统;但如果按履约建模法去建,它才会变成一个真正可治理、可审计、可追责的数据平台。
如果面试官追问“履约建模法在这里最有价值的点是什么”
我觉得最有价值的点,是它逼着我先去找平台自己的承诺关系和凭���流,而不是一开始就沉进技术字段里。这样最后建出来的数据结构天然更稳定,也更适合长期治理。
最后一句收尾
所以如果让我总结,我不是用履约建模法去描述某个业务事件,而是用它先把埋点平台自己的定义、提交、验收和交付关系建清楚。最后得到的数据结构也不是单张事件表,而是一整套围绕证据链展开的平台模型。