工作原理
契约包含三个层次。结构层定义模式和类型;语义层定义含义,例如 revenue 包含什么、使用什么货币、时间戳是否为 UTC、null 代表什么;运营层定义服务水平,包括新鲜度、完整性、唯一性和事件联系人。好的契约还会说明如何宣布变更,以及消费者能提前多久得到通知。
强制执行把文档变成保证。生产者在发布前按契约校验输出;CI 对上一版本运行兼容性检查;平台监控新鲜度和数据量,并在承诺未兑现时通知负责人。消费者因此可以针对稳定接口构建,而不是逆向工程文件。
何时使用
对跨团队边界、支撑财务或监管报表、或驱动共享看板的数据集使用契约。从影响最大的数据集开始,而不是每张表都上契约。契约最有价值的地方,是静默变更代价高昂,且生产者确实能够被要求兑现承诺。
权衡
契约增加评审和协调开销,可能拖慢需要快速行动的生产者。字段规定过细会让演进痛苦,规定过粗又让消费者暴露在风险中。契约必须由有权限执行的人拥有,否则会变成过时文档。只有模式的契约会漏掉造成大多数事故的语义失败。
示例与误解
事件团队承诺 order_created 中 order_id 唯一、amount 以最小货币单位表示、时间戳为 UTC、五分钟内送达。若生产者改为十进制单位,契约测试会在部署前失败,消费者不会看到被破坏的收入数据。
误解是订阅模式注册表就等于契约。注册表检查结构;它不承诺新鲜度、含义,也不承诺数据停止时有人响应。
代码示例
-- 每个邮箱保留 created_at 最新的一行,标记其余为重复
WITH ranked AS (
SELECT
id,
email,
created_at,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY created_at DESC, id DESC
) AS rn
FROM subscribers
)
SELECT id, email, created_at, (rn = 1) AS is_primary
FROM ranked
ORDER BY email, rn;
-- 一次性清扫:删除全部非首行
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY created_at DESC, id DESC
) AS rn
FROM subscribers
)
DELETE FROM subscribers s
USING ranked r
WHERE s.id = r.id
AND r.rn > 1;
-- 上游堵漏:唯一索引阻止重复再次写入
CREATE UNIQUE INDEX CONCURRENTLY idx_subscribers_email
ON subscribers (email);
package main
import (
"fmt"
"math/bits"
)
type Bitset []uint64
func NewBitset(size int) Bitset {
if size < 0 {
panic("negative bitset size")
}
return make(Bitset, (size+63)/64)
}
func (b Bitset) Set(index int) {
b[index/64] |= uint64(1) << (uint(index) % 64)
}
func (b Bitset) Clear(index int) {
b[index/64] &^= uint64(1) << (uint(index) % 64)
}
func (b Bitset) Has(index int) bool {
return b[index/64]&(uint64(1)<<(uint(index)%64)) != 0
}
func (b Bitset) Union(other Bitset) Bitset {
size := len(b)
if len(other) > size {
size = len(other)
}
out := make(Bitset, size)
for i := range out {
var left, right uint64
if i < len(b) {
left = b[i]
}
if i < len(other) {
right = other[i]
}
out[i] = left | right
}
return out
}
func (b Bitset) Intersect(other Bitset) Bitset {
size := len(b)
if len(other) < size {
size = len(other)
}
out := make(Bitset, size)
for i := range out {
out[i] = b[i] & other[i]
}
return out
}
func (b Bitset) Count() int {
total := 0
for _, word := range b {
total += bits.OnesCount64(word)
}
return total
}
func (b Bitset) Slice() []int {
out := make([]int, 0, b.Count())
for wordIndex, word := range b {
for word != 0 {
bit := bits.TrailingZeros64(word)
out = append(out, wordIndex*64+bit)
word &^= uint64(1) << uint(bit)
}
}
return out
}
func main() {
active := NewBitset(80)
for _, id := range []int{1, 5, 64, 79} {
active.Set(id)
}
blocked := NewBitset(80)
blocked.Set(5)
blocked.Set(9)
fmt.Println("count:", active.Count())
fmt.Println("has 64:", active.Has(64), "has 65:", active.Has(65))
fmt.Println("members:", active.Slice())
fmt.Println("union count:", active.Union(blocked).Count())
fmt.Println("intersect:", active.Intersect(blocked).Slice())
active.Clear(64)
fmt.Println("after clear:", active.Count())
}