← 中文资源

简体中文版本

规范数据模型

规范数据模型定义一个系统中立的共享模式,各集成系统向它双向映射,N 个系统只需 2N 份映射而非 N×N 份点对点契约。

阅读英文原文

概念定义

两两集成需要每对一份映射——随系统数二次增长。规范枢纽格式让每个系统只映射到枢纽:线性数量的映射、一套共享词汇,以及唯一一处强制语义的地方。它是“公共协议”在数据侧的对应物。

工作机制

模型以系统中立方式定义实体、标识符、枚举与不变量:生产方翻译出站,消费方翻译入站。模式注册中心跨版本校验兼容性;映射代码集中在一处做集中测试,而非散落各对之间。

使用场景

三个以上系统交换同类实体、发布节奏各异的 B2B 数据流、聚合异构源的分析平台。仅两个系统一次性集成不值得引入这层间接。

常见误区

委员会式设计容易膨胀:应追逐最小稳定语义,其余做版本化扩展。过度抽象的模型不可读,欠规范的模型则让每个映射方各自发明语义。标识符策略——谁拥有规范主键——是错伤最久的决策;无版本闸门的破坏性变更会同时打断所有消费者。

代码示例

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true, coerceTypes: true, removeAdditional: true });
addFormats(ajv);

const schemas = {
  CreateUser: {
    type: 'object',
    additionalProperties: false,
    required: ['email', 'role'],
    properties: {
      email: { type: 'string', format: 'email' },
      role: { type: 'string', enum: ['admin', 'editor', 'viewer'] },
      display_name: { type: 'string', maxLength: 60 },
    },
  },
};

const validate = ajv.compile({
  $ref: '#/components/schemas/CreateUser',
  components: { schemas },
});

function schemaMiddleware(validator) {
  return (req, res, next) => {
    if (!validator(req.body)) {
      return res.status(400).json({
        error: 'schema validation failed',
        details: (validator.errors ?? []).map((e) => ({
          path: e.instancePath || e.params?.missingProperty ?? '',
          message: e.message,
        })),
      });
    }
    next();
  };
}

const ok = validate({ email: '[email protected]', role: 'editor' });
const bad = validate({ email: 'not-an-email', role: 'hacker', extra: 1 });
console.log('valid:', ok, 'errors:', bad ? 0 : validate.errors.length);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OrderState {
    Draft,
    Placed,
    Paid,
    Shipped,
    Cancelled,
}

#[derive(Debug, PartialEq, Eq)]
enum TransitionError {
    InvalidTransition {
        from: OrderState,
        event: &'static str,
    },
}

struct Order {
    state: OrderState,
}

impl Order {
    fn new() -> Self {
        Self {
            state: OrderState::Draft,
        }
    }

    fn apply(&mut self, event: &'static str) -> Result<OrderState, TransitionError> {
        use OrderState::*;
        let next = match (self.state, event) {
            (Draft, "place") => Placed,
            (Placed, "pay") => Paid,
            (Paid, "ship") => Shipped,
            (Draft | Placed, "cancel") => Cancelled,
            (_, _) => {
                return Err(TransitionError::InvalidTransition {
                    from: self.state,
                    event,
                });
            }
        };
        self.state = next;
        Ok(next)
    }
}

fn main() {
    let mut order = Order::new();
    assert_eq!(
        order.apply("pay"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Draft,
            event: "pay",
        })
    );
    assert_eq!(order.apply("place"), Ok(OrderState::Placed));
    assert_eq!(order.apply("pay"), Ok(OrderState::Paid));
    assert_eq!(
        order.apply("cancel"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Paid,
            event: "cancel",
        })
    );
    assert_eq!(order.apply("ship"), Ok(OrderState::Shipped));
    assert_eq!(
        order.apply("ship"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Shipped,
            event: "ship",
        })
    );
    println!("final state: {:?}", order.state);
}