有赞订单导出业务隶属于有赞交易订单管理组,主要职能是将有赞商家的订单数据通过报表的形式导出并提供下载给商家使用。目前承接了有赞所有的订单导出业务,报表的字段覆盖交易、支付、会员、优惠、发货、退款、特定业务等,合计超过 100 个。
随着有赞的迅速发展,有赞的行业、业务与产品覆盖面越来越广。从行业角度来看,覆盖了微商城、新零售、餐饮、美业、教育等,从模块角度来看,覆盖了交易、资产、客户、营销、店铺等,从产品角度来看,覆盖了分销、精选等。每个行业、模块、产品都会在订单导出报表中有所诉求。如下图所示,展示了有赞订单导出的域模型:
订单导出需要跨越来自不同行业、不同产品、不同模块,对各个业务域的存储和设计有整体理解;同时,需要通过技术手段(数据域、存储域、报表域、文件域)聚合来自各个域的数据集合,生成可读的报表下载给商家。
由此可见,其主要的挑战是:如何快速支持各个域灵活多变的导出字段需求。如何应对这一挑战呢?
订单导出的最初实现是从交易的多个 DB 及多个业务 API,分别获取交易、支付、会员、发货、退款、核销、分销等多个数据,组装到一起生成报表。采用 PHP 任务脚本来实现。这种做法有两个痛点:
基于这两个痛点,有赞订单管理组进行了架构升级,详见有赞技术博文《有赞订单管理的三生三世与“十面埋伏》。 得益于此,订单导出也迁移到基于 ES + Hbase 的技术栈。其中订单搜索采用 ES 服务实现,订单详情则存储在 Hbase 中,通过 API 来获取。整体流程如下所示:
重构之后,订单导出的性能和稳定性有了很大的提升:
接下来,开始了配置化之旅。
订单导出常常要面临添加新的报表字段的需求。最初实现不太灵活,是来一个字段,在代码流程里添加一个字段。每次增加新的字段,都需要修改多处。因此,第一个优化是采用函数接口编程,将字段定义做成枚举可配置化的,然后遍历指定的报表字段列表,拿到对应的字段定义,计算字段的值,写入报表文件。
根据报表字段列表生成报表行的伪代码如下:
public List<String> generateReportLineData(List<String> fields) {
return StreamUtil.map(fields, field -> {
try {
FieldDefinition fieldDef = getFieldDefinition(field);
FieldMethod method = getMethod(fieldDef);
String value = method.invoke(this.reportItem);
return postproc(value);
}catch (Exception e){
logger.warn("failed to get value for field: {} orderNo: {}", field, reportItem.getOrderNo());
return "";
}
});
}
这个小小的优化,为进一步的配置化设下伏笔。当需要新增报表字段时,只要增加新的字段定义,而不需要在流程里增加代码。增强软件可扩展性的一个重要方法是,将流程变得通用,只要增删流程里的环节及定义即可。
凡基础必要总是正确的方向。
有赞新零售、餐饮的迅速兴起和发展,需要低成本快速地搭建起零售和餐饮的订单导出。这要求订单导出具有更大的灵活性,能够根据不同行业的要求配置不同的字段列表及导出格式,同时又能互不影响。此外,不同商家有个性化的导出需求。然而,原来的订单导出,是专门为微商城开发的商品级别的报表。要加一个字段,往往会影响所有的有赞商家,使用体验不佳,订单报表本身也变得臃肿不堪。
如何突破原来的局限,支持更灵活的订单导出呢?这是订单导出面临的一个破局点。通过订单导出模板解决了这个问题。针对行业、产品配置的导出模板存储在 DB 表 export_biz_conf 里;针对有赞商家的导出模板存储在 DB 表 export_customized_conf 里。每个导出模板包括了如下信息:报表字段列表、导出维度(订单及商品)、报表文件格式、可选项等,做到足够灵活。
若要导出不同报表字段,只要新增相应字段,指定报表字段列表即可;若要生成不同维度的报表,可使用策略模式。比如,
如图所示: 针对导出流程的各环节,可采用策略模式来选择不同实现,然后将策略组合起来。
通过实现报表配置功能,突破了之前的局限,可以支持不同行业、产品的标准化和定制化导出需求,并且做到相互隔离不干扰。
随着有赞进入更多的行业,面临着更加多变和个性化的导出需求。比如,有赞教育需要导出知识订单的学员信息和课程信息,有赞零售需要导出导购员和发货仓库门店名称。 显然,如果要完成某个导出需求,还需要修改代码、发布系统,这种操作会非常频繁,导致开发和维护成本提升,影响系统稳定性。
如果能够在应用运行中动态地新增报表字段并加载和使用,无需修改导出工程代码,无需重新发布系统,就能更加快速地支持导出需求,将会大幅降低导出需求支持的开发和维护成本,保持系统稳定性。
为了解决这个问题,引入了动态脚本语言 Groovy. Groovy 是能够与 Java 无缝对接的好伙伴,可以直接使用 Java 类的功能。编写 Groovy 脚本实现报表字段逻辑,存储在字段配置表 export_field_conf 里, 在报表配置表 export_biz_conf 或 export_customized_conf 里引用,然后在应用启动时缓存到内存里并使用。比如粉丝姓名的 Groovy 脚本如下:
import com.youzan.trade.orderexport.util.PublicUtil
def fansInfo = reportItem.orderInfo.extra["FANS"]
PublicUtil.fetch(fansInfo, "nickname")
PublicUtil 是导出工程里封装的一个工具类,可以让编写字段配置脚本更加简单。值得提及的是,为了避免使用 Groovy 脚本可能导致的内存泄露,需要对编译后的 Groovy 脚本进行缓存和执行。
为了实现无需改动代码和发布系统,还需要在整体流程上打通。整体流程如下:
Step1: 当用户下单后,源数据落到业务数据库的扩展信息里;
Step2: 通过数据同步,自动同步到 Hbase 表;
Step3: 通过 Apollo 配置和可扩展的数据聚合机制,将数据自动输送到用来计算报表字段值的报表对象里;
Step4: 新增报表字段的配置;
Step5: 在报表配置中引用该字段的标识。
下图展示了通过配置自定义字段快速支持导出需求的整体流程。
整体流程打通后,当需要新增个性化字段时,通常只要做两步:
个性化字段配置能力已经在线上稳定运行,比如拼团订单成团时间、零售导购员、有赞教育的课程字段等。
紧接着,订单导出又面临分销采购单的导出需求。分销采购单导出流程跟订单导出有所不同,需要分别导出分销买家订单和供货订单的详情信息再导出。这个流程跟通用的订单导出流程是有所区别的。如果通过修改订单导出的通用流程来支持,显然会影响所有的订单导出,使订单导出流程不清晰。
最终采用的解决方案是:对分销采购单的导出需求和所需技术进行抽象,实现一个更加通用的导出能力模型,支持交易领域的各种潜在导出需求,而不仅仅局限于分销采购单导出。通过分析订单导出流程可以发现,绝大多数导出都遵循如下核心流程:
可以将核心流程做成插件式的。首先,定义一个插件接口,包含其配置和功能等;其次,实现常用的插件列表,支持从 ES, HBase, API 查询或获取数据,以及常用的过滤、排序、格式化、生成报表等功能;最后,将这些插件列表串联成一个具体的导出实例。整体流程则采用模板方法模式复用了订单导出流程。
比如微商城分销采购单导出通过依次执行ES查询插件、订单详情插件、数据排序插件、报表字段格式化插件、报表生成插件来实现,其中订单详情插件针对分销买家单和供货订单分别调用了一次。
前面提到,订单导出的报表字段非常多,导出数据量大,如何保证代码改动或重构后订单导出的服务质量和数据准确性?主要手段如下:
此外,采用函数编程及设计模式,使代码实现层面更具复用性和柔软性。18K 行代码,代码重复率约为 1.8%。
本文简要讲述了有赞订单导出的配置化实践。通过配置化之后,订单导出的能力和稳定性有了大幅提升。当然,还有一些需要提升的地方。比如,可以增加扩展点机制,允许业务方定制化导出;局部细节可以打磨得更细腻。欢迎对海量订单业务感兴趣有经验的小伙伴与我们一起共建订单管理大局!简历可直邮 shuqin@youzan.com.
在这个过程中,有许多小伙伴给予了有力的支持,比如产品同学对订单报表的细致的规划设计、客满运营同学提出的及时反馈、有赞技术团队的支持以及自己的付出。
欢迎关注我们的公众号
Original url: Access
Created at: 2019-09-26 16:05:03
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论