假若,你是某个国内电商平台的商品中心项目负责人。突然今天,接到了一个这样的需求:商品在原人民币价格的基础架构上,须支持卢比(印度)价格。
需求
需求点,可以描述为:
- 购买的用户,商品价格需要支持卢比;
- 营运人员,商品管理系统依然使用人民币价格;
同样这个需求,定了以下两个硬指标:
- 必须实现需求;
- 必须快速上线;
问题
首先,我们必须承认的是,这确实是个简单的需求,但这也是个够坑爹的需求。主要遇到的问题如下:
- 涉及商品价格的系统众多;
- 各上层系统调用商品价格接口繁多;
- 商品价格相关字段较多;
为了实现快速上线,我们在原人民币的商品价格基础架构上,只能进行少量且合适的改造。所以,最后我们的改造方向为:尽量只改造商品价格源头系统,即商品中心,其他上层系统尽量不改动。
可行性调研
改造商品中心,商品价格支持卢比。可行的改造方案有 2 种:
1、数据表价格字段存卢比
将原人民币价格相关的数据表字段,存卢比值,数据表并新增人民币字段。
2、接口输出数据时转化为卢比
原人民币相关的数据表字段依然存人民币值,在接口输出数据时,将价格相关字段值转化为卢比。
针对以上方案,我们需要注意 2 个问题:
- 汇率会每天变化,所以商品价格也会变化;
- 后续商品价格,可能须支持多币种;
上述 方案 ①,商品中心只需改造数据表。然后每天根据汇率刷新商品价格,原价格字段就都变成了卢比。方案相对简单,也容易操作,但缺点是:对任然需要人民币价格的系统,即商品管理系统须改造。
方案 ②,需要改造商品中心业务逻辑。由于涉及的价格字段较多,改造较复杂,主要优点是:汇率变动对商品价格影响较小,且可拓展支持多币种价格(可以根据地区标识,获取相应的商品价格)。
解决方案
最终,为了系统的可扩展性,我们选择了方案 ②。
这里主要改造了商品中心,主要解决 透传地区标识 和 支持多币种价格 这 2 个问题。
透传地区标识
我们的业务系统主要分为 API 和 Service 项目,API 暴露出 HTTP 接口,API 与 Service 和 Service 与 Service 之前使用 RPC 接口通信。由于商品中心涉及到价格的接口繁多,不可能对每个接口都增加地区标识的参数。所以我们弄了一套调用链路透传地区标识的机制。
机制原理
思路就是,先将地区标识放在全局上下文中,API 接口通过 Header 头X-Location
携带地区标识;而对于 RPC 接口,我们的 RPC 框架已支持了 Context,不需要改造。
代码实现
传递全局上下文
由于 RPC 框架已支持了 Context,所以 API 和 RPC 接口透传全局上下文略有不同。实现如下:
class Location |
上述
init()
方法,需要在项目入口位置初始化。
其中,RPC 接口不需要操作全局上下文。因为 RPC Client 在调用时会自动获取全局变量$context
值并在 RPC 协议数据中追加 Context,同时 RPC Server 在收到请求时会自动获取 RPC 协议数据中的 Context 值并设置全局变量$context
。
RPC Client 传递 Context 实现如下:
protected function addGlobalContext($data) |
RPC Server 获取 Context 实现如下:
public function getGlobalContext($packet) |
当设置了 Context 后,RPC 通信时协议数据会携带location
字段,内容如下:
RPC |
设置地区标识
到这里,我们只需要在全局上下文设置地区标识即可。一旦我们设置了地区标识,所有业务系统就会在本次的调用链路中透传这个地区标识。实现如下:
class Location |
获取地区标识
设置了地区标识后,就可以在本次调用链路的所有业务系统中直接获取。实现如下:
class Location |
支持多币种价格
商品中心
有了地区标识后,商品中心服务就可以根据地区标识对价格字段进行转化了。因为设计到价格的数据表和价格字段较多,这里直接从数据层(Model)进行改造。
改造获取数据方法
下述的ReadBase
类是所有数据表 Model 的基类,所有获取数据表数据的方法都继承或调用自getOne()
和getAll()
方法,所以我们只需要改造这两个方法。
class ReadBase |
后缀匹配价格字段
由于涉及到价格字段名字较多,且具有不确定性,所以这里使用后缀方式匹配。为了防止一些字段命名不规范,这里引入了黑名单机制。
protected function isExchangeField($field) |
前缀为
is_
的字段一般定义为标识字段,默认为非价格字段。
计算地区价格
上述getExchangePrice()
方法,用来根据地区标识转化价格覆盖到原价格字段,并自增以_origin
后缀的人民币价格字段。
public function getExchangePrice(&$data) |
其中,getExchangePrice()
方法会调用Location::get()
获取地区标识,并根据汇率计算实时价格。
最终,商品中心改造后,得到的部分商品价格信息,如下:
# 人民币价格10,汇率10.87 |
API系统
对于所有 API 的项目,我们只需要让客户端在所有的请求中增加X-Location
头即可。
GET /product/detail/1 HTTP/1.1 |
API 项目需在入口文件处,初始化地区标识。如下:
Location::init(); |
商品管理系统
对于商品管理系统,我们为了方便运营操作,所有商品价格都应以人民币。因此,我们只需要初始化地区标识为中国,如下:
Location::init(); |
总结
为了实现需求很容易,但是要做到合理且快速却不简单。本文的实现的方案,避免了很多坑,但同时也可能又埋下了一些坑。没有一套方案是万能的,慢慢去优化吧!
