引言
我们知道搭建系统跟投放系统是两个紧密关联的系统,搭建产出的是页面的结构,投放产出的是页面的数据。搭建产出的页面包含各式各样的模块,这些模块包含的字段也没有太多规律可言,那么投放系统怎么为这些模块补全数据呢?
在回答这个问题之前,我们先尝试解决一些简单的业务 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
方法。
字段映射配置
数据源配置