Java服务_ck服务弹性接口service层和dao层设计实战1
1.背景
在ck等olap数据服务层,为了抽象化我们创建的接口,扩展接口的可服用性,我们一般会把接口设计成弹性接口。弹性接口的意思就是,传入的维度参数列表是可选的、可变的,传出的指标数据列表也是可选的、可变的。
如下表所描述的一个接口为例来对弹性接口进行理解:
| serviceName | getBrandIndBrandRank |
|---|---|
| demensions | STime_ETime_SecondIndId_[ThirdIndId]_[ShopType]_[TerminalId]_[BrandId]_[PriceRangeId] |
| indicators | BrandId_BrandName_RankRound_DealAmt_DealProNum_PV_UV_DealCustPriceAvg_DealRate_CartUser |
| options | PageNum_RowNum_OrderBy |
| attributes |
首先,维度组合是该接口的入参列表,按照属性我们一般将维度划分为demensions、options。对于弹性接口,就设置了一些可传可不穿的参数,上表中中括号中的demensions维度即为弹性维度,全部options都是弹性维度。通过控制传入的维度参数,就可以通过一个接口实现多维度数据的查询。
指标组合就是该接口返回的出参列表,按照属性我们一般将指标划分为indicators、attributes。弹性接口的指标就全部都是可选的,只需要在入参中执行本次请求需要哪些指标即可。
排序字段和分页字段就是比较典型的options维度。
2.接口文档设计
1.交易-交易特征-渠道特征
| serviceName | getTradeChannelFeature |
|---|---|
| demensions | STime_ETime_ShopId_ShopLevel_MainItemScndCatgCd |
| indicators | DealUserNum_DealOrderNum_DealProNum_DealAmt_DealRate_CatgAvgDealAmt_ChannelId_SecondChannelId |
| options | |
| attributes |
2.交易-交易特征-类目特征
| serviceName | getTradeCategoryFeature |
|---|---|
| demensions | STime_ETime_ShopId_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealProNum_DealAmt_DealRate_SecondIndId_ThirdIndId_ThirdIndName |
| options | |
| attributes |
3.交易-交易特征-品牌特征
| serviceName | getTradeBrandFeature |
|---|---|
| demensions | STime_ETime_ShopId_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealProNum_DealAmt_BrandId_BrandName |
| options | PageNum_RowNum_OrderBy |
| attributes |
4.交易-交易特征-商品价格带特征
| serviceName | getTradePriceDistrictFeature |
|---|---|
| demensions | STime_ETime_ShopId_PriceDistrictList_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealProNum_DealAmt_DealRate_PriceDistrict |
| options | |
| attributes |
5.交易-交易特征-新老客户特征
| serviceName | getTradeOldNewCustomerFeature |
|---|---|
| demensions | STime_ETime_ShopId_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealAmt_CustomerType |
| options | |
| attributes |
6.交易-交易特征-支付方式特征
| serviceName | getTradePayTypeFeature |
|---|---|
| demensions | STime_ETime_ShopId_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealAmt_PayTypeId_PayTypeName |
| options | |
| attributes |
7.交易-交易特征-客单件数特征
| serviceName | getTradeSaleQuantityFeature |
|---|---|
| demensions | STime_ETime_ShopId_SaleQuantityList_[ChannelId] |
| indicators | DealUserNum_DealOrderNum_DealAmt_SaleQuantity |
| options | |
| attributes |
3.创建dao层param类
dao层的param类实际上就是承接接口入参列表的bean类,一般直接借助fastjson组件将jsonObject对象转化为param类对象。
此处我们想要使上述七个弹性接口共用一个param类,那么只需要将这七个接口所涉及到的维度组合全部放进来即可。
创建param类如下:
1 | package com.jd.ad.dao.bean.sztrade; |
注意添加@JSONField注解设置json字段与param属性的对应关系。
4.创建工具类将set转化为字符串
可以看到param类中的属性都有get、set方法,其中get方法非常重要,是mybatis动态代理生成dao方法时获取这些属性所使用的方法。也就是说mapper.xml文件中的占位符参数真实值都是通过这些get方法获取的。
那么对于Set<String>类型的属性,毫无疑问我们需要在get方法中将它转化为一个String类型的值再放入sql占位符中。
最常见的两种转化方法:
1.将Set中的字符串元素直接用逗号拼接起来:
1 | public static String getStrBySet(Set<String> sets) { |
2.将Set中的每个字符串元素都加上单引号,再用逗号拼接起来,因为在ck中使用单引号标记字符串类型:
1 | public static String getQuotationStrBySet(Collection<String> sets) { |
5.创建dao层的mapper接口类
dao层的mapper接口类是mybatis基于spring框架生成dao方法实例的基础,供service层依赖引用。
1 | package com.jd.ad.dao.mapper.sztrade; |
6.创建继承整个微服务抽象类的service方法
整个ck数据微服务的service层使用一个抽象类来获取所有数据查询接口的方法名等信息:
1 | package com.jd.ad.service; |
然后所有的service类都需要继承该抽象类。
我们在service类中,为了优雅和美观,将七个接口抽象到同一个query方法中:
1 | package com.jd.ad.service.impl.sztrade; |
可以看到query方法中有几个比较重要的工具方法:
1.ReqUtils.generateParam()将jsonObject转化为parambean类对象
2.ReqUtils.generateCols()生成查询信息
3.SZTradeFeatureUtil.setQueryCols()设置查询列信息
4.SZTradeFeatureUtil.setPriceDistrict()设置价格带信息
5.SZTradeFeatureUtil.setSaleQuantity()设置客单价带信息
可以看到上述工具方法前两个是整个service层共用的,后三个是该service类七个弹性接口共用的,下面我们会一一创建并介绍。
7.创建ReqUtils类与工具方法
一个请求jsonObject实例如下:
1 | { |
1.generateParam()方法将jsonObject转化为parambean类对象:
1 | public static Object generateParam(JSONObject jsonObject, Class<?> clazz) { |
很明显,该方法中最主要的步骤,就是把传入的维度组合dimensions、options放入param类对象中。
2.generateCols()生成查询信息:
1 | public static Set generateCols(JSONObject jsonObject) { |
很明显,该方法最主要的步骤,就是从传入的参数中获取本次请求所需要的指标组合indicators、attributes。
8.写出一个接口查询全量指标的sql
做这一步的目的是为了观察sql,从7个sql中抽象出共性较大的弹性查询字段:
getTradeChannelFeature
1 | select |
getTradeCategoryFeature
1 | select |
getTradeBrandFeature
1 | select |
getTradePriceDistrictFeature
1 | select |
getTradeOldNewCustomerFeature
1 | select |
getTradePayTypeFeature
1 | select |
getTradeSaleQuantityFeature
1 | select |
从以上7个sql中明显可以抽象出4个比较常用的指标字段组合:
1.与表没有绑定关系的一个外层指标字段组合:
1 | DealUserNum as DealUserNum, |
2.与ck_sz_trade_featrue_shop_deal_det_d表绑定的一个指标字段组合:
1 | uniq(userLogAcct) as DealUserNum, |
3.与ck_sz_trade_featrue_shop_traffic_det_d表绑定的一个指标字段组合:
1 | uniq(brwsUniqId) as uv |
4.与ck_sz_trade_featrue_shop_index_info_d表绑定的一个指标字段组合:
1 | avg(ordAmt) as CatgAvgDealAmt |
9.创建SZTradeFeatureUtil类与工具方法
1.setQueryCols()设置可弹性变化的查询列:
在上述总结的可以弹性变化的指标字段组合的基础之上,创建对应的四个查询指标组合Set<String>字段,根据入参中传入的indicators和attributes指标组合来构建这四个字段,并放入到param类对象中,方便后续以占位符的形式放入sql中。
1 | public static void setQueryCols(Set<String> cols, SZTradeFeatureParam param) { |
2.setPriceDistrict()设置价格带上限和下限:
对于价格带和客单件带这种可随机变化的区间查询条件,很明显难以在一次db请求中以较为优雅的sql把结果查询出来,那么我这里采用循环查询的方式,每一个价格带请求一次db。该工具方法用来将价格带上、下限参数放入到param类对象中,方便后续以占位符的形式放入sql中。
1 | public static void setPriceDistrict(String priceDistrict, SZTradeFeatureParam param) { |
3.setSaleQuantity()设置客单件带上限和下限:
与价格带同理,该工具方法用来将客单件带上、下限参数放入到param类对象中,方便后续以占位符的形式放入sql中。
1 | public static void setSaleQuantity(String saleQuantity, SZTradeFeatureParam param) { |
10.完善service类中的query方法
我们统一使用List<LinkedHashMap<String, Object>>数据类型来承接ck数据库查询sql返回的查询结果,该List中的每个LinkedHashMap元素就是返回的一行数据;然后该LinkedHashMap中的每个entry元素就是一列数据,key为列名,value为数据值。
所以在query方法中我们可以使用如下代码将多次价格带请求合并在一起,并分别打上PriceDistrict字段的值:
1 | for (String priceDistrict : priceDistrictList) { |
11.创建dao层的mapper.xml配置文件
创建这个文件最重要的就是优雅,实现优雅的根本秘诀就是抽象与复用,大体顺序就是resultMap-select-dbsql-tablesql-wheresql:
1 |
|
12.在spring-config-mybatis配置文件中创建与ck数据源bean和mapper的bean
读取配置文件中配置的ck主备集群url、username、password,通过xml配置文件的形式创建ck数据源bean和mapper的bean:
1 | <!-- 商家版离线交易特征数据源 --> |