Skip to the content.

BFF

2023/09/07

接口数据结构的声明

在使用TypeScript的声明后台的所给的接口数据的时候,因后台是外部因素,前端无法直接控制,接口字段是否都用Partial修饰,变为可选?

这个问题,直接的考虑:

两种办法都有问题。是否有更好的办法呢?

外部系统不可信原则

上面接口数据结构的分析,都没有跳出前端和后台耦合到一起的思维。

实际项目中,应该遵循外部系统不可信原则。前端作为相对独立的子系统,任何外部的数据,都不可信。

因此,在接收的时候,就需要做好必要的校验。有异常数据,做好上报告警。并跟进数据的异常,来判断前端此时是否继续运行。

既然后天所给的接口数据不可信任,我们就可以有一下的考虑:

数据接入层(充血模型的class)

更进一步,我们考虑充血模型的class的细节和优点。

前端页面在设计的时候,很容易犯一个错误,缺乏统一的数据接入层,也缺乏对应的Model。直接使用后台接口返回的所有数据字段。或者直接从页面组件View中发起请求,更新View的内容。

直接使用API返回数据,后端的Model已经侵入到前端页面。无形中,对具体的后台有依赖。

因此,在一个应用中,对于第三方I/O(Http,JsBridge等),应该放到单独的Service中。View无需关注Service的实现,只关注Service的接口即可。这样就隔离了第三方I/O的影响。

in-adapter

这个Service还可以支持:

  1. 校验数据;失败,则抛出异常,展示兜底页面;
  2. 设置默认值;
  3. 格式化数据:获取返回的数据以后,格式化数据,将后台的数据,映射为前端所需的数据结构;
  4. 作为adapter:支持多个不同的后台,特别是后台数据因种种原因无法保持一致的时候,在Service中可以统一格式;
  5. mock数据:在开发、调试或自动化测试阶段,也可以mock数据,解耦前后端依赖;

// 前端数据协议
type DataType = {
  attribute: string
};

// 后台定义的数据协议
type HttpDataType = {
  httpAttribute: string
};

// 默认值
const DEFAULT_DATA: DataType = {
  attribute: ''
};

class DataConversionService implements DataType {
  private pAttribute: DataType['attribute'] = DEFAULT_DATA.attribute;

  constructor() {}

  // 数据校验
  public hasAttribute(obj: unknown): obj is HttpDataType {
    return (obj as HttpDataType)?.httpAttribute !== undefined
        && typeof (obj as HttpDataType).httpAttribute === "string";
  }

  public convert(data: unknown) {
    if (this.hasAttribute(data)) {
      this.pAttribute = data?.httpAttribute;
    } else {
      // 日志
      console.log('缺少关键数据');
      // 上报
      // 抛出错误
      // 走兜底等等
    }
  }

  public get attribute(): DataType['attribute'] {
    if (this.pAttribute) {
      return this.pAttribute;
    } else {
      return DEFAULT_DATA.attribute;
    }
  }

  public set attribute(obj: unknown) {
    this.pAttribute = obj as DataType['attribute'];
  }
}

数据接入层的Service,隔离了对外的数据的依赖,只要数据通过数据接入的校验、格式化等,View中就可以放心的使用Service中的数据,不用在每一个用到的地方再次校验。

同时,有了这个统一的数据接入层,在页面加载的时候,可以很方便的处理页面的Loading、Success和Failed状态。

BFF

阅读资料

log