← 中文资源

简体中文版本

数据契约

数据契约是生产方与消费方之间就数据模式、语义与质量达成的显式且可测试的约定。

阅读英文原文

概念定义

数据契约是生产方向消费方承诺的带版本规范:字段名与类型、允许取值、可空性、新鲜度与数据量范围,以及归属人。它在 CI 与边界处强制执行,使破坏性变更无法悄悄抵达消费方。模式只描述结构,契约还约束行为——数据迟到如何处理、删除记录怎样处理、检查失败时呼叫谁。

为什么重要

在模块化的数据平台里,没人知道某张表的全部下游用途。一次悄悄的改名、一个新引入的空值或一次时区偏移,可能同时打断看板、模型与合规报表。契约把这些突发状况变成经过评审、公告与版本化的变更,这正是“事故”与“日常发布”的区别。

如何落地

从被消费最多、风险最高的数据集开始,把消费方早已假设的约定写下来。在生产流水线的每次构建中校验它,发布生成的文档,并把违规路由给生产方的值班人。破坏性变更给一个新的主版本并留出迁移窗口。久而久之,“数据如何表现”的部落知识就变成了无需人工即可运行的检查。

代码示例

<table>
  <caption>
    Employee Directory - Q1 2025
  </caption>

  <colgroup>
    <col class="name-col">
    <col class="dept-col">
    <col class="role-col">
    <col class="salary-col">
  </colgroup>

  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Department</th>
      <th scope="col">Role</th>
      <th scope="col" class="numeric">Salary</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">Alice Johnson</th>
      <td>Engineering</td>
      <td>Senior Developer</td>
      <td class="numeric">$95,000</td>
    </tr>
    <tr>
      <th scope="row">Bob Smith</th>
      <td>Marketing</td>
      <td>Manager</td>
      <td class="numeric">$85,000</td>
    </tr>
    <tr>
      <th scope="row">Carol Davis</th>
      <td>Engineering</td>
      <td>Team Lead</td>
      <td class="numeric">$110,000</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <th scope="row" colspan="3">Average Salary</th>
      <td class="numeric">$96,667</td>
    </tr>
  </tfoot>
</table>

<style>
  table { border-collapse: collapse; width: 100%; }
  th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #ddd; }
  .numeric { text-align: right; }
  caption { font-weight: bold; margin-bottom: 0.5rem; }
</style>
import java.util.List;
import java.util.Objects;

public class RecordClasses {

    public record Point(int x, int y) {
        public Point {
            if (x < 0 || y < 0) {
                throw new IllegalArgumentException(
                    "Coordinates must be non-negative");
            }
        }

        public double distanceTo(Point other) {
            return Math.sqrt(
                Math.pow(x - other.x, 2) +
                Math.pow(y - other.y, 2)
            );
        }
    }

    public record Rectangle(Point topLeft, Point bottomRight) {
        public int width() {
            return bottomRight.x() - topLeft.x();
        }
        public int height() {
            return bottomRight.y() - topLeft.y();
        }
        public int area() {
            return width() * height();
        }
    }

    public record Employee(
        String name,
        String department,
        double salary,
        List<String> skills
    ) {
        public Employee {
            Objects.requireNonNull(name);
            skills = List.copyOf(skills);
        }
    }

    public static void main(String[] args) {
        Point p1 = new Point(0, 0);
        Point p2 = new Point(3, 4);
        System.out.println("Distance: " + p1.distanceTo(p2));

        Rectangle rect = new Rectangle(p1, p2);
        System.out.println("Area: " + rect.area());

        Employee emp = new Employee("Alice", "Engineering", 95000,
            List.of("Java", "Python", "SQL"));
        System.out.println(emp);
    }
}