如何设计投放系统系列----灵活的字段映射补全机制

引言

我们知道搭建系统跟投放系统是两个紧密关联的系统,搭建产出的是页面的结构,投放产出的是页面的数据。搭建产出的页面包含各式各样的模块,这些模块包含的字段也没有太多规律可言,那么投放系统怎么为这些模块补全数据呢?

在回答这个问题之前,我们先尝试解决一些简单的业务 case

几个案例

案例一

有一个商品模块,字段包括:商品的标题、商品图片、购买链接、商品价格、商品描述,想要投放某个选品集的商品,请问投放系统应该怎么设计以补全这些字段信息?

拿到这个需求,最直观的解决方案就是,直接去商品库取选品集对应的商品列表,把商品库里的字段塞到对应的模块字段上。

// 商品选品集
var goodSet = [1,2,3,4,5];

// 从商品库获取对应的商品实体信息
var entityMap = goodService.fetch(goodSet);


var output = [];
for (var i = 0, l = goodSet.length; i < l; ++i) {
  var goodId = goodSet[i];
  var entity = entityMap[goodId];
  if (!entity) {
    continue;
  }
  // 字段映射
  output.push({
    '商品名称': entity['goodName'],
    '商品图片': entity['goodImg'],
    '商品链接': entity['goodLink'],
    '商品价格': entity['goodPrice']
    ...
  });
}
return output;

案例二

有一个店铺模块,字段包括:店铺的名称、店铺的照片、店铺链接,想要投放某个选品集的店铺列表,请问投放系统应该怎么设计以补全这些字段信息?

这个需求跟上面一个类似,依然是最直观的解决方案,直接去店铺库取选品集对应的店铺列表,把店铺库里的字段塞到对应的模块字段上。

// 店铺选品集
var shopSet = [1,2,3,4,5];

// 从店铺库获取对应的店铺实体信息
var entityMap = shopService.fetch(shopSet);


var output = [];
for (var i = 0, l = shopSet.length; i < l; ++i) {
  var shopId = shopSet[i];
  var entity = entityMap[shopId];
  if (!entity) {
    continue;
  }
  // 字段映射
  output.push({
    '店铺名称': entity['shopName'],
    '店铺图片': entity['shopImg'],
    '店铺链接': entity['shopLink']
  });
}
return output;

案例三

同样一个商品模块,字段包括:商品的标题、商品图片、购买链接、商品价格、优惠价格,想要投放某个选品集的商品,请问投放系统应该怎么设计以补全这些字段信息?

这个 case 我们发现单纯的从商品库取不到优惠价格信息,必须去另外一个服务获取商品的优惠价格。

对应的伪代码为:

// 商品选品集
var goodSet = [1,2,3,4,5];

// 从商品库获取对应的商品实体信息
var entityMap = goodService.fetch(goodSet);


var output = [];
for (var i = 0, l = goodSet.length; i < l; ++i) {
  var goodId = goodSet[i];
  var entity = entityMap[goodId];
  if (!entity) {
    continue;
  }
  // 字段映射
  output.push({
    '商品名称': entity['goodName'],
    '商品图片': entity['goodImg'],
    '商品链接': entity['goodLink'],
    '商品价格': entity['goodPrice'],
    // 去优惠券服务获取商品的优惠价格
    '优惠价格': couponService.fetch(goodId).price
  });
}
return output;

到了这里,我们发现模块只要一变,代码就得跟着变,有没有办法能模块变化,代码不变呢?

终极解决方案

相信聪明的你已经可以看出,随着需求的变化,我们的代码变化的都是字段的补全来源以及字段的映射关系,不变的是整个代码的流程。如果我们可以把这些变化的部分做成可配置的,似乎代码就不需要变动了。

我们试着写了这样一个接口

function get(id, field);

这个接口只需要传实体的 id,以及需要返回的字段名,就可以返回对应的值。

每个字段的具体补全逻辑都是 get 的具体实现,我们把实现做成可配置的形式

商品数据源配置
{
    'goodName': {adapter: 'goodService', param: ['id', 'goodName']},
    'goodPrice': {adapter: 'goodService', param: ['id', 'goodPrice']}
    'couponPrice': {adapter: 'couponService', param: ['id', 'couponPrice']}
}
店铺数据源配置
{
    'shopName': {adapter: 'shopService', param: ['id', 'shopName']},
    'shopImg': {adapter: 'shopService', param: ['id', 'shopImg']}
}

接着我们再添加一些配置,配置的是模块素材字段跟数据源中的字段ID的映射关系

商品字段映射
模块素材字段编码 => 数据源字段 ID
{
  '商品名称': 'goodName',
  '商品图片': 'goodImg',
  '商品链接': 'goodLink',
  '商品价格': 'goodPrice',
  '优惠价格': 'couponPrice'
}

店铺字段映射
{
  '店铺名称': 'shopName',
  '店铺图片': 'shopImg',
  '店铺链接': 'shopLink'
}

最后我们再修改下伪代码:


function get(id, field) {
    var param = configService.getDatastoreConfig(field).param;
    return adapterFactory.get(field).apply(param);
}


// 选品集
var entitySet = [1,2,3,4,5];
// 实体类型
var entityType = model.entityType;
// 获取字段映射配置
var fieldMappingConfig = configService.getFieldMappingConfig(entityType);

var output = [];
for (var i = 0, l = entitySet.length; i < l; ++i) {
  var entityId = entitySet[i];

  var entity = {id: entityId};
  // 遍历模块的字段列表
  for (var j = 0; j < model.fields.length; ++j) {
    // 模块字段编码
    var field = model.fields[j];
    // 补全该字段的值,get 会用数据源配置的类和参数补全该字段的值
    entity[field] = get(entityId, fieldMappingConfig[field]);
  }

  output.push(entity);
}
return output;

上面其实也是我们 UTCP 系统目前的设计思路。

get

get 接口对应的就是 AbstractEntity.get 方法。

字段映射配置

image.png

数据源配置

image.png

猜你喜欢

转载自yq.aliyun.com/articles/608832