「订单」业务的设计与实现
Posted Java程序员老张
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「订单」业务的设计与实现相关的知识,希望对你有一定的参考价值。
一、背景简介
订单业务一直是系统研发中的核心模块。订单的产生过程与系统中的许多模块高度关联,例如账户体系、支付中心、运营管理等等。即使仅单独考虑订单本身,它也足够复杂了。
在业务发展过程中,订单量必然会持续增加,订单自身、数据量和实现流程都需要不断迭代更新。如果在订单流程的研发初期没有全面考虑,很可能导致中后期需要重构。
基于实践经验,围绕订单业务,建议过度设计,采用轻量级分步实现。
在产品初期,应该先做好全面的设计,保留场景和流程的可扩展性,规划不同数据量的应对方案,走在订单业务前沿,避免被动,尽量不要被业务发展和演变甩在身后。
二、订单业务
1、订单体系
从角色上看,订单体系主要涉及用户、商户和平台三个核心参与方。订单流程的搭建就是围绕三方的交易场景展开。
这里需要说明一些细节:商户可以是第三方商家,也可以是平台方自己,不影响概念上的划分;商品也存在多种形式,因此用“交付”一词来描述可以覆盖物流的定义;
用户通过应用端选择商品并下单,平台则负责实现订单交易链路和支付能力,并对整个流程进行调度;商户则提供商品和交付能力。
在图中,只是围绕订单体系做了一个框架性的宽泛描述。在成熟的订单业务中,其复杂程度远超上图。下面将围绕核心节点进行细致分析。
2、流程管理
2.1 流程拆分
订单的业务属性非常高,流程本身也比较复杂。从不同的参与方来看,其流程分段策略完全不一样。在这里,我们仅从研发视角,把订单逻辑分为三个阶段:创建、支付和交付。
- 订单创建:通过管理用户的下单路径,从商品的访问点击和选中,到购物车下单或直接下单,从而完成订单的创建。
- 订单支付:各种支付渠道的对接是交易场景的基础功能,订单的核心状态即支付成功。
- 订单交付:在订单支付完成后,开始进行商品的交付流程,可能是商家的发货或服务提供,交付成功即订单完成。
如果将整个订单场景统筹起来看的话,还存在很多隐性的流程,与订单衔接的上下游业务还有很多,这里只是专注于订单功能自身的边界做划分;
2.2 正向流程
在理想的状态下,订单从购物车结算下单开始,到交易支付完成,最终到商家完成交付,是非常复杂的流程链路;
在实现上,订单的正向流程链路都是分段管理的,比如购物车、订单创建之后、支付完成、交付等诸多关键节点,并不是一个即时的流程;
2.3 逆向流程
针对订单这种极度复杂的流程,可能导致订单流程逆向的情况,需要仔细考虑并提供相应的解决方案,以确保程序可以兜底流程逆向。人工干预的成本和风险都极高,因此需要尽量避免。
- 取消动作:用户主动取消订单,发起退款流程等;商户因为交付失败,主动发起退款流程等动作;
- 超时情况:订单创建后,指定时间内没有支付;订单支付后,指定时间内商家没有交付等多个超时场景;
- 节点异常:系统平台在订单调度时的业务异常,或者程序异常,又或者支付等第三方渠道异常等。
这些常见的异常问题,在一般场景下可能不会引起效果问题。但是,在像订单这样异步解耦的复杂场景中,需要一个稳定的机制来快速执行逆向流程。例如,下单后未支付导致持续锁定库存,或者交付超时会影响用户体验等问题。
2.4 调度与监控
订单属于核心流程,同时具有复杂的特性,因此自然而然地依赖于系统平台的调度和监控手段。无论是正向流程还是逆向流程,都需要调度手段来提高订单的完成率,或者促使逆向流程有序执行。在这个过程中,需要具备对订单路径的完整监控能力。
调度机制:侧重处理订单的被动状态,通常用于各种超时场景,旨在提前向用户和商家发送通知消息或处理订单流程。
监控策略:主要处理订单的主动干预,当订单中断或出现异常时,可以通过产品入口进行主动修复,或通过系统层面的主动重试,最终也可以进行手动干预。
3、结构设计
围绕订单场景,涉及的数据结构非常复杂,不论是商品还是支付,亦或是订单自身的结构,在具体的业务中都会拓展出很多关联表;
订单结构的设计和管理,基于场景复杂度考虑,可能要融合商家、仓储货架、用户、渠道和类型等;在订单量增长之后,还需要结合业务场景,进行数据体量层面的拆分处理;
三、技术方案
1、订单ID
订单主体的唯一ID标识,在数据量不大的情况下,可以使用表的自增ID主键。但从长期来看,这并不是一个友好的方式。如果订单量大,可能需要进行分库分表的流程,这时需要制定ID生成策略。
有以下几种ID生成策略可供选择:
- UUID:生成唯一字符串识别码,直接使用订单ID即可。
- 雪花算法:分布式ID生成算法策略,生成的ID遵循时间的顺序。
- 自定义ID:除了唯一的属性外,在订单ID中添加其他的关键业务标识。
2、并行与异步
在订单详情的加载过程中,有很多查询信息需要涉及,如商品、商户、订单、用户等。这些查询信息可以用并行操作的方式来处理,以提高响应时间。如果采用串行方式,则接口性能会大幅下降。在并行操作中,可以采用多线程或多进程的方式并行处理不同的查询信息,从而使整个查询过程更加高效。此外,为了保证并行操作的正确性,还需要考虑并发控制和同步机制等问题。
异步操作是指将复杂的流程分成多个步骤进行处理,以提高服务性能。在订单处理中,采用分段异步的常规方式,可以通过使用MQ消息来实现。这种方式可以大大提高服务性能。无论是订单的正向流程还是逆向流程,都可以采用基于状态、事件和动作的异步解耦处理方法。因此,可以在订单处理中采用分段异步的方式,以确保流程的高效性和可靠性。
3、超时问题
订单超时问题的本质在于,需要在指定时间段之后执行一个动作。举个例子,在最经典的场景中,如果下单之后超过15||30
分钟未支付,订单将自动取消并关闭,释放商品的库存,并通知用户。
实现动作延迟执行的方式有很多,比如延迟队列、过期监听、消息延时消费等。但在复杂的订单系统中,这些方式并不常见。相对来说,采用定时任务调度的方式更为主流。
在任务调度过程中,对订单的处理同样需要确保业务流程操作的幂等性和数据层面的一致性等问题。如果出现异常单,则需要进行重试,并不断分析异常原因以优化流程。
如果订单体量很大,任务调度还能胜任吗?
订单体量和订单实时量不是同一个概念。系统中积累的订单量和任务要处理的量也不是同一级别的。通过对数据进行分库分表的设计和查询优化,就可以有效处理体量较大的订单,不会成为调度任务的瓶颈问题。
如果订单数据实时体量很大,比如每天超过千万?
这已经不是应用问题了。如果订单量每天达到千万级别,公司会提前很长时间将数据团队引入应用团队中,以解决这种核心的难题。我曾在数据公司工作时,每天单量刚刚过百万,就已经安排数据团队制定解决方案了。
4、分布式事务
订单涉及支付对接、库存管理、结算对账等各种复杂的流程,因此对数据一致性有极高的要求。如果数据层面出现问题导致异常单出现,难免需要人工介入处理。因此,对流程的各阶段做好细致的事务和逻辑管理非常重要。
订单流程是异步解耦的方式推进的。在分布式事务的策略上,我们追求的是最终结果的一致性,这并不妨碍在分段的流程中进行局部的事务管理。事务成功则流程正向推进,事务失败则流程重试或逆向回滚。
四、数据方案
1、转化分析
经典的订单指标体系可用于深度分析用户下单过程中的路径统计,以解决转化率问题。通过不断优化流程和场景,可以提高成交量。一些例子包括:
- 优化购物车页面,使其更加用户友好,以提高用户的购买意愿。
- 为新用户提供优惠券,以吸引他们尝试购买。
- 稳定的供应链管理,以确保产品能够及时到达客户手中。
- 通过多种方式提供付款选项,以满足不同用户的需求。
- 与社交媒体平台合作,以增加品牌曝光率,吸引更多用户。
交易的转化路径分析,是产品和运营重点关注的指标体系,在数据层面,埋点采集的数据通常是上传第三方平台,方便进行用户和业务分析,并且有助于同类客群的营销推广;
2、分库分表
随着数据量的逐渐增大,数据的管理和维护变得越来越困难。如果不进行有效的处理,可能会导致性能下降,甚至系统崩溃。因此,我们需要对数据进行分库分表的操作,以便更好地管理和维护数据。
分库分表是一种将数据按照特定的规则分散到不同的数据库中的处理方式。这种方式可以有效地解决数据库读写瓶颈问题,并提高数据库的性能和稳定性。通过对订单数据进行分流,我们可以将数据分散到不同的库表中,从而实现数据的高效管理和维护。
在进行分库分表操作时,需要考虑到数据的一致性和可靠性。因此,我们需要设计合理的数据分配策略,确保数据的完整性和准确性。同时,还需要制定有效的备份和恢复策略,以防数据丢失或损坏。
总之,分库分表是一种有效的数据管理和维护方式,可以提高系统的性能和稳定性。在处理大量数据时,我们应该考虑采用这种方式,以便更好地管理和维护数据。
基于订单ID计算拆分的逻辑是最常见的,在特殊情况下,也会基于用户ID或商户ID进行计算,从而将相关的数据堆放在一起,如果有必要,也可以考虑多维度拆分的多写模式;
3、数据同步
订单数据分库分表虽然解决存储问题,但是也带来了很多查询方面的阻碍,通过搜索引擎来解决查询问题也是常用的技术选型;
订单数据在库和搜索引擎之间的同步方法有很多,每种方法都有其优缺点和适用场景。以下是几种同步方式的详细介绍:
- 同步双写:这种方法对数据的实时性要求极高,可以保证库和搜索引擎之间的数据同步几乎是实时的。但是,由于要在两个系统之间进行频繁的数据同步,可能会影响系统性能,需要考虑系统的扩展性和稳定性。
- 异步解耦:这种方法将数据同步过程解耦,流程存在轻微的延迟,但可以保证系统的稳定性和性能。适用于数据同步量较大,但对实时性要求不是特别高的场景。
- 定时任务:这种方法通过定时任务来同步数据,可以保证数据同步的时效性,但存在明显的时效问题,可能会造成数据的不一致。适用于对实时性要求不高,但对数据准确性要求较高的场景。
- 组件同步:这种方法采用第三方数据同步组件来进行数据同步,可以保证系统的稳定性和性能,同时减少开发和维护成本。适用于数据同步量较大,但对实时性和数据准确性要求都比较高的场景。
根据订单场景的特点,推荐使用同步双写的方式进行数据同步。但是,在实际应用过程中,还需要根据具体情况进行分析和优化,以达到最佳的数据同步效果。
用领域驱动设计实现订单业务的重构
大家好,我是来自罗辑思维得到app的韩宇斌,很荣幸能有机会和大家分享我的一些心得,我分享的主题是《DDD战略建模在重构业务系统时的实践》。我分享的内容分为三部分:第一部分是:用领域驱动来把握真正的业务需求;第二部分:领域驱动设计指导架构设计与建模;第三部分:用限界上下文来保护领域。
一、用领域驱动来把握真正的业务需求
如果把我今天的分享比作一个故事的话,那么故事的主线是:领域驱动设计帮助我解决了工作的难题。这个难题表现在两个方面,首先无路可退 :入职第一个任务,做不成就意味着回家。其次是左右为难 :实现技术重构的目标,满足不了业务需求!不去实现,又不知道该做什么?
在进入到主题之前,我们先来从商家视角了解一些电商业务的背景知识。一个商家想要卖自己的商品,不管是在线下实体店面,还是线上电商,至少要有卖货,收钱,发货三个环节,如果我们从网上找一个开源的商城系统,也会包括这三部分的功能。然而,对于商家来说,只有这三个环节是不够的,还需要有一个非常重要的环节就是算账。我分享内容,就是和收钱,发货,算账相关的。
我们再来看一下得到APP电商业务涉及的组织和系统。我所在的听书属于业务后端,主要负责卖货和发货环节,而收钱环节需要由交易平台组和基础平台组提供的服务来完成,算账相关的由财务平台组负责。大家先对红框的三个系统有个印象,后面会频繁的提到。
得到app目前如何确认收入呢?财务部门在算账交税前,首先要确认收入,卖了多少商品,收了多少钱,实收账款和应收账款能不能对上。用户在APP内下单后生成订单记录,收到钱后生成支付记录,发货生成权益记录。虚拟商品的发货不需要快递,就是我们在表里面写一条数据。财务部门需要把三张记录表中的每条数据都用订单号关联起来,并且状态含义严格匹配,才能确认为是收入,否则就会成为坏账或者呆账,需要人工处理。
然而,开始的时候,并不是这样的。用户在APP中购买虚拟商品,我们没有生成订单记录,只有支付记录和权益记录。当出现数据不一致时,就给财务确收带来了问题。主要分为两类,只有支付记录没有权益记录数据,是有支付无交付,我们收了钱没发货;而只有权益找不到对应的支付记录的数据,是有交付无支付,发货了没收到钱。每个财务结算周期,两个开发组的人,几乎都要去排查问题数据。你也许会好奇,一个电商平台居然没有订单?我相信“存在即合理”,当时这么做肯定有当时的原因和背景,说白了一切都是为了快速上线,快速验证得到app的商业模式,活下去对于创业公司来说,比设计实现一个完美的系统优先级更高。
我们看下没有订单的情况下,系统之间的调用关系。为了说明核心问题,我挑选了流程最简单的节操币购买的方式,节操币是得到APP内的虚拟货币,节操币系统是由我们的基础平台组负责的。以购买听书的内容为例,APP去请求听书系统的购买接口,然后由听书系统调用节操币系统完成扣款,扣款成功后写入已购。
这张图是没有订单时,听书业务实现全部售卖方式的调用关系,从图中我们可以看到,听书系统需要直接与许多外部系统交互,系统间的依赖和耦合是比较高的。
我们再回到财务结算的场景。由于没有订单,给财务核算工作带来很多问题,财务就要求必须记录订单及交易状态。于是各业务系统就花时间去改造,用户购买时先调用订单系统的CreateOrder来生成订单记录,支付完成后要调用PayOrder来标记订单的支付状态,发货后调用SignOrder来把订单签收,我们内部把增加订单的这几个动作叫做“订单化”。实现了订单化以后,财务就可以按照订单记录、支付记录、权益记录的严格匹配来进行核算与确认收入了。
我们看一下实现了“订单化”后的调用关系,听书系统需要增加3个请求去调用订单系统,而原来的每个接口都要传递订单ID。例如创建完订单以后,在扣除节操币的环节,要告诉节操币系统是为具体的哪笔订单扣除余额的。
实现了订单化后,业务系统对外部系统的依赖和耦合有些加剧,增加了对订单系统的三次调用,同时要给别的系统调用请求中传递订单号。
上线后没多久,原系统架构带来的痛点很快就出现了。财务要求在订单加个“签收时间”字段,据说20多天才上线!为什么需要20多天,因为所有的业务系统都要修改相关的代码,而测试人员要把所有业务的购买都要回归测试一遍,在需求开发时间非常紧张的情况下,投入与
财务又提了需求,要求尽快把所有交付的内容实现“订单化”,因为我们除了有资金往来的购买,还有很多用户免费领取的方式,这些也是要核算成本和交税的。如果再来一次前面那样的修改,可要命了。于是团队思考该怎么办?结论是内部实现个系统,代理全部订单相关的功能,这样再有修改,只改这个代理服务就行了!实现隔离变化!
这就是我接到的重构任务:订单代理(订单化)系统。实现 一个代理服务,对接 交易平台组的订单系统和基础平台组的支付系统,推动若干个业务系统改造,改成调用新的代理服务。
我们看下订单代理系统是如何隔离了变化的。从图中我们看到,业务系统不在直接依赖外部系统的了,订单相关的参数由代理系统去组织,如果再有订单相关的修改,只改这个代理服务就行了。还以增加“签收时间”的字段为例,业务系统就不需要关注这个字段了。
表面看,我们的设计方案, “同时满足”了业务需求和技术目标。业务需求:所有的商品都实现“订单化”:技术这边不光都实现“订单化”,还实现个“订单化的代理系统”,应对外部系统的变化。
这张图很多人一定都看到过,他描述的是对用户需求理解偏差造成的软件项目的失败,在我这个场景,方案确定了!但这是业务的目标吗?
我读了些代码并做了系统分析后,带着掌握的内容,满怀信心的去和合作部门交流,却感觉大家关注的点甚至方向都常常不一致!!!在需求理解上,我们和业务方存在认知的偏差。开发最关心的是,完成全部商品的订单化,实现订单代理系统,降低业务系统与外部系统的耦合。而业务关心的是,一定要正确的交付(面向现在),能够高效准确的算账(面向未来),把过去的账给解释清楚(面向过去),我给总结为三个面向。订单代理系统的目标在财务那里只是个过程!他们甚至丝毫不关心我们是不是要实现一个订单化的代理系统。真正的目标需求是什么???如果一个系统还没有开始做,你就知道即使做完也达不到业务的目标,那种心情是很纠结的。
这就是前面说的,我面临的挑战。首先是无路可退,入职的第一个任务。第二是左右为难:实现“订单代理系统”,满足不了业务需求!如果不去实现“订单代理系统”,那该做什么?
思考了没有把握到真正需求的原因。技术人员理解到的所谓“需求”是一种内部视角,是有局限性的。而业务方是外部视角,看到的要比我们全面。领域驱动设计,它能让技术人员和业务都能从外部视角——也就是领域来看问题。
DDD思想指导的开发过程,是一个全程强调领域的过程,开发人员和领域专家,从业务需求中提炼出【统一语言】,基于统一语言建立【领域模型】,用领域模型指导设计及编码实现。
从财务的诉求中,我们把握到了需求的问题域是:电商的发货与算账,而业务的期望是精确交付。
前面说了很多订单相关的,也做了很多分析,但是我所要实现订单代理系统,在整个罗辑思维这个电商业务平台中,是个什么地位呢?带着问题我去找业务沟通。下面的图,是一次找财务方向的产品经理沟通讨论时给我画的,产品经理说第一次有技术主动和她聊财务相关的业务,一高兴就给我讲了很多。
为了让自己的理解和产品经理想要表达的不产生太大的偏差,当天结合这个草图,赶紧画了一个自己理解的图,第二天又去给产品经理讲了一遍。反述的过程,自己明白订单化在全局的位置,虽然貌似不起眼但是却担负着得到所有虚拟商品的交付。
经过继续深入调研后,把“订单化”要完成的内容,划分成了支付和交付两部分,而所在的得到后端,应该关注得到商品的交付部分。
和业务的充分沟通与协作,渐提炼出来了一些“统一语言”,成为了我们的交易领域内的业务语言。例如,订单完整的生命周期:下单,支付,交付,签收。而确收的内容指的是:已购权益和时间权益。
在领域驱动设计思想的指导下,我们找到了真正的业务需求,并不只是开始的,实现一个订单代理系统那么简单,而是要实现“财务核算级别的精确交付”。
二、领域驱动设计指导架构设计与建模
在第一部分,我们找到了真正的业务需求,第二部分,来介绍一下“领域驱动设计指导架构设计与建模”。在这部分,业务的真正需求将会落地。
我们再来看一下电商业务的基本模型。我们在电商平台所完成的的购买行为,其实就是买卖双方,围绕着交易物,以双方认可的价格签订合同后,展开的履约行为。签约生成的合同,就是订单,买方付钱后合同开始生效,卖方收钱发货,买方收货签收后,合同结束。卖方还要具备卖货,算账等能力。
我们想一下,当一个人或者一个组织具备了电商业务的全部功能后,他是不是就可以成为“个体户”或者“小商贩”了呢。“个体户”和“小商贩”用自己劳动换来回报,值得我们尊敬。但是在一个电商平台中,每个业务系统都把自己做成“小商贩”系统,并不是件好事。
我们再来看下订单代理系统架构的弊端。首先,每个业务都是个“小商贩”,相同功能的代码依然会重复。即使业务系统把原来直接调用外部系统的方式改成调用订单代理系统,交付数据的准确性依然达不到财务要求,因为无论是业务系统的开发还是产品经理,对于交付领域和交付数据都缺乏足够的敏感度,毕竟术业有专攻。
小商贩模式能够不那么优雅的解决技术的问题,但却不能满足业务“财务核算级别的精确交付”的需求,因为交易领域中缺少一个专注交付的子领域。于是,我们重新理解和确定了领域问题。在前面的概念模型中,我们得到了几个概念:交易是基于合同的行为,订单是合同,交付是履约。得到后端的核心子领域问题:是“履约”是交付,到此,一个订单交付系统就呼之欲出了。
在我们这个场景中,业务系统实现了交易的全部功能,就是个“小商贩”。每个小商贩都卖货收钱发货,而和现实中小商贩不同的是,每个商贩都不对卖货产生的账务数据负责,而账务却是由一拨人算的。我们通过DDD的限界上下文划分来分析,就会发现,应该把每个业务上下文中和交易相关的功能独立出来,把自己变成一个商品的供货商,入驻超市卖场,让专业的人去做专业的事,业务系统自己变为“超市卖家”。
由订单交付系统接管业务的交易行为,原来听书商品的购买,变成了这样的调用关系。我们的订单交付系统前置,直接和app对接,创建订单和交付统一由交付系统负责完成,原业务系统只需要提供一个返回商品价格、上下架状态的接口,就可以完成售卖。我们用这样的方式,让订单交付系统从所有的业务方接管了售卖交付行为,而业务方几乎不需要做什么开发,甚至感觉不到订单的存在。
最终,订单交付系统满足了业务和技术的目标。业务方不再是“小商贩”,入驻“超市”成为“卖家”,交付的数据达到财务精准核算的要求。绿框部分,就像一个超市,业务系统将自己的商品上架后,就不需要关注收钱、交付和算账了,可以把更多的精力投入到自己业务产品的研发中,
三、是用限界上下文来保护领域
首先,我们来认识一下,强调上下文的重要性。我抽象了一个乘客到飞机场登机的例子。
机场,为了维护自己的秩序,是要采取许多措施来保护这个领域的。而我们识别了领域后,也要保护领域。保护是手段,目标:边界内的“完美世界”不可侵犯,完美世界是什么,我们想一下架构优良,职责单一,代码整洁等等让人感觉到美好的词语。保护的依据:就是识别了限界上下文后划分的边界,以及限界上下文内的规则。
在订单交付系统开发和推进的过程中,采取了一些保护这个领域的措施,我按照上下文之间和上下文内部总结为如下的分类。上下文之间,第一、规范可以进入上下文内的对象模型,第二、用领域事件解耦合与其它上下文的关系,第三、把握领域的职责,第四、隔离上下文间的业务内涵。上下文内部,保护业务抽象行为的一致性。后面我们结合实例,来看一下具体的行动措施。
1、保护领域的行动:规范可以进入上下文内的对象模型。
《领域驱动设计》一书中解释限界上下文时,Eric Evans 用细胞来形容限界上下文,认为“细胞之所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。”当进入机场上下文的时,“人物”要变为“乘客”;当进入订单交付上下文的时,“业务对象”要变为“商品”。
为什么要这么做,因为在之前,进入各业务上下文的模型是由业务自己决定的,客户端结算台是根据许多的if……else来判断该请求哪个业务系统的接口。而由于缺乏领域规范,这些参数形态各异,业务端的实现也是各不相同,导致出现问题排查排查困难。
所以,在新的订单交付系统推广的第一步,就是通过统一参数来规范进入交付领域的对象。App开发新的功能和我们对接时,我们要求不管原来是什么类型的业务对象,进入订单交付领域,必须转换为商品,必须传递ProductID + ProductType。而对于老版本的业务,我们通过直接在网关转发时,把接口转到我们的防腐层,转换为商品再去调用交付逻辑。
2、保护领域:用领域事件解耦与其它上下文的关系。
由于购买数据有很多系统会关注,所以在之前,每记录一条用户权益,就会至少给四个相关上下文推送格式大同小异的消息供相关方消费,浪费资源不说,维护成本也高,还被外部的上下文业务规则所绑架。磐石,关心商品sku和价格,勋章关系价格和数量,已购只关心数量,大数据方面关心商品和价格。4个需求方,不同时间段提的需求,由于没有之前没有领域,就都是定制开发。在重构新的订单交付系统时,我们认为交付结果推送的消息是一个领域对外提供的服务,应该统一由领域来提供标准外部去适配,而不是外部提要求我们来定制开发。于是,就在记录用户权益后,发布统一的领域事件,由外部系统的上下文来订阅和适配。
3、保护领域:把握领域的职责,领域之外的事少管。
在之前,用户的充值工作完全是由得到后端来全权负责的,要反复和订单系统和支付系统交互,前后至少要经过9次调度和响应,极易出现问题,所以在财务对账中,充值也是被财务吐槽比较多的一个地方。前面分析过,交付的节操币这个商品其实不属于得到后端的商品,我们做了那么多,是出力不讨好。
交易中心是我们中台化的一个产物,这个系统接管了包括订单和支付的所交易行为,所以在和交易中心对接时,我认为既然节操币不属于得到后端,就不应该由我们来负责交付,于是我们就把节操币充值的交付,“让”给交易中心。充值行为变成了这样的调用关系,我们的订单交付系统比原来少做了很多事情,全局的充值结果却更有保障了,因为减少了不必要的调用。红线部分,是完成同样的充值行为,我们订单交付系统需要做的事情,连订单的支付和签收也不需要我们关心了。
4、保护领域:使用上下文隔离相同事物的不同内涵。
之前,用户在得到商城内购买一个课程,要先产生一个商城订单,然后再推送给得到后端,会再生成一个得到的订单并交付签收。在财务审计和对账中,这个逻辑也带来了很多的问题数据,因为两笔订单的对应关系是很不稳定的。一次购买,产生两笔订单,并不符合我们这个场景。
一次购买,产生两笔订单的适用用场景,适用于两个商家彼此之间是完全独立核算的场景。我之前有个同事,开了个网店,但是她一点货都不存。她是怎么玩的呢?从网上把别的商家的图片商品信息抓过来,在自己的网店上加10~100块钱不等就上架,当用户在她的网店下单后,她再去别的网店下个同样商品的订单,收货人填买家。由真正的卖家发货。这个场景下,虽然看着是同一个东西,但内涵上其实是两个不同的事物,如果严格点,肯定是两个不同的sku。
而我们这个场景,只收了一份钱,内部也没有再次产生资金流水行为,商城上下文和我们订单交付上下文应该是合作关系,一起接力完成一笔订单的交付。虽然在不同的上下文中,表现为订单和交付单两个不同的东西,但内涵上是同一个事物的不同状态。
于是,在对接这部分业务时,我们打破了原有规则,说服大家接受商城购买是一种下单途径,流转到得到这边,是订单履约的方式,就是一个订单。这样做以后,商城同步售卖的订单核算,可以和其它支付类型购买的商品采用一样方式核算了,技术人员再也不用给财务导两笔订单的对应关系了。
5、保护领域:保护业务抽象行为的一致性。
我们先看一下订单交付系统实现层面的设计。使用了桥模式,一边抽象了支付方式,另一边抽象了商品的交付。中间通过统一的交付逻辑来控制。
在重构对接老业务的时候,遇到了一个历史产品,会破坏“业务抽象行为一致性”,会破坏我们的完美世界,使我们的代码和架构坏腐。用户只要购买过这个产品,那么在有效期内可零元购买“得到app”的所有内容,为了实现这个产品功能,代码中几乎所有购买场景,都要增加if……else……,即破坏设计,污染代码。这个产品一张一年,而有的用户,一下子把这个产品的有效期买到了2037年。
记得一次开会,我们的老板说过这个产品未来可以下线,我就记下了。所以当这个老的产品要再来破坏我们领域内的完美世界时,我就决定有法可依的去推进这个产品的下线。当选择不写代码去解决业务问题,必然就要去做很多沟通协调方面的工作。这虽然是一个极端的例子,但是可以说明我们技术人员要有保护领域的决心。
我分享的内容即将结束,最后来说一下,DDD指导的设计建模带来的几个长尾收益:可以快速支持商城直接售卖各种商品,可以快速接入书单等多商品打包的购买,可以快速接入各种产品的赠送功能,客户端可以封装统一的结算台组件,其中最我认为重要的是我们可以逐渐从每月核查财务数据的工作中解放了……
最后的结语是:不要把DDD只当做一门技术来学习,ta可以是指导开发流程的方法论。
往期推荐:
技术琐话
以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。
以上是关于「订单」业务的设计与实现的主要内容,如果未能解决你的问题,请参考以下文章