标签:
前面的文章,我介绍了Conference案例的业务、上下文划分、领域模型、架构,以及代码整体流程。接下来想针对案例中一些重要的场景,分别做进一步的分析。本文想先介绍一下Conference案例的核心业务场景 - 订单处理减库存的设计。
上面用文字描述了整个下单和订单处理以及支付的过程,而我们实际关心的核心还是服务端对订单处理的过程。所以下面我们就看看如何来进行代码实现。
Conference案例中,服务端处理订单是采用CQRS Saga流程的方式实现的。一个Saga流程是一个事件驱动的业务流程,它的周期可能比较长,因为流程中某些步骤需要用户参与的。上图描述了服务端处理订单的正常处理逻辑。为什么说是正常处理逻辑,因为实际的代码比上面的流程图还要复杂一点,上面的流程图中没有画出库存不足、用户拒绝付款、或者付款超时等情况的处理。我觉得这些特殊的情况,只要读者自己看一下代码就能很快理解了。只要我们能够把正常的逻辑搞清楚,那我们心里就对整个订单处理的流程有了解了。
上图中,聚合根之间棕色的箭头表示Command,蓝色的箭头表示Event。Order Process Manager表示一个无状态的Saga流程管理器,它负责协调其他有状态的聚合根,负责整个订单处理的流程控制逻辑。从代码表现上来看,它的任务就是响应Event,然后发出下一个Command。然后Order, Conference, Payment三个聚合根分别表示订单、会议、支付。这三个聚合根分别封装自己的状态和业务规则。
大家都知道,电子商务系统,订单处理时,核心的环节就是减库存。那我们首先要思考的问题是,库存在哪里维护呢?在我看了微软的Conference案例的代码后,发现它的库存信息是在Registration(订单处理)的上下文中维护的。当ConferenceManagement(会议管理)上下文中,对会议的库存有修改时,会通过事件异步同步到订单处理上下文。我在思考它这样设计的理由是什么,我能想到的唯一理由是,这样的好处是减库存时,就只需要在Registration当前的上下文中处理即可,这样就不需要依赖会议管理上下文了。但代价就是需要从会议管理上下文同步库存信息。
我的思考:
我个人认为,库存信息还是应该在会议管理上下文中维护比较合理,因为会议管理上下文的职责就是维护会议的基本信息以及会议的座位类型的实体信息。如果我们的库存管理没有独立为独立的上下文,那最合理的维护地方就是会议管理上下文。这样,一份数据就只需要在一个地方维护,不需要同步。然后当订单处理上下文需要减库存时,可以通过远程调用或者异步消息通信的方式来实现上下文之间的交互。
但实际的电商系统,比如像淘宝这种,由于库存管理也是一块复杂的业务,所以一般会独立出一个上下文,叫库存中心。然后这个库存中心独立于商品中心以及订单中心。当订单中心要求减库存时,只需要和库存中心进行交互即可。这样的方式,会让系统的职责更明确,商品中心不需要关心商品的库存了,只需要关注商品本身的属性信息即可。然后,本案例由于只是案例,所以没有独立出库存中心,即库存上下文。所以会议座位的库存管理放在会议管理上下文中。
明确了库存所属的上下文后,我们接下来思考怎么实现减库存。减库存主要的问题是,在并发减库存的情况下,可能会出现超卖的情况。为了解决超卖的问题,一般主流的做法是采用预扣库存的方式,类似分布式事务的二阶段提交的过程。预扣的意思是先预扣库存,如果预扣成功,就可以通知用户下单成功,然后就可以让用户去付款了;如果预扣时发现库存不足,则提示用户库存不足。
然后,虽然是预扣,但因为大家同时预扣同一个会议聚合根的座位库存,所以还是会产生并发问题。但由于我们操作的是同一个聚合根,所以ENode框架帮我们确保不会有并发问题。我们先看看Conference聚合根内部关于座位的库存管理的设计实现。
如上面的代码所示,Conference聚合根里聚合了所有的座位类型子实体,每个座位类型维护了座位的名称、价格、数量;然后Conference聚合根里还维护了所有的预定记录,这个应该不难理解。MakeReservation方法就是Conference聚合根对外提供预定座位支持的方法。该方法接收一些要预定的项,以及一个预定的ID,表示这次预定是谁(实际上就是订单ID)要预定。该方法内部的逻辑是:
Domain不会考虑并发这种技术问题,它只关心自己的业务规则和数据一致性,完全从业务角度来写代码。我们可以看到Conference聚合根里封装了很多的规则和逻辑。然后Conference聚合根产生的Event持久化到EventStore时的并发问题,ENode框架会帮我们解决,应用开发者不用担心了。如果大家关心是怎么解决的,可以去看一下ENode我以前写过的一些介绍,核心思路是乐观并发控制(通过聚合根版本号)+ 自动重试的机制,这里我就不展开了。
通过上面的设计,我们知道每次预扣时总是会判断当前可用的库存,并且已经考虑了其他已经预扣了的订单;这就从业务逻辑上保证了不会出现超卖;然后ENode框架解决了并发问题,所以最后我们可以确保一定不会出现超卖的情况。
当预扣成功后,用户就会去付款,假如付款成功了,那系统就会自动提交之前的预扣记录,做真正的减库存。我们来看看逻辑是怎么样的:
CommitReservation方法是Conference聚合根用来提供支持提交减库存的方法。它接收一个要提交减库存的reservationId,通过该ID,先找到之前它预定的所有预定项,然后产生一个事件,事件中包含每个预定项所对应的座位类型的扣除后的库存数量,最后产生领域事件。然后聚合根内部会响应领域事件,更新聚合根自己的状态。我们在Commit阶段是不用担心数据有什么问题的,因为肯定是之前预扣过了,只要预扣记录存在,那就可以放心的做减库存逻辑的。这是我们通过业务上的2PC协议保证的。
代码很直接,就是先删除预定记录,并把预定记录的每个明细对应的座位类型的库存更新即可。然后,我们的读库的更新也是这样的逻辑,只是更新的是读库DB而已。
好了,本文基本把订单处理的核心环节减库存讲了一下,本来还想再结合订单状态的变更讲一下订单状态在这个过程中是如何变化的。但由于今天时间比较完了,不准备讲了。我在前面的领域模型的介绍中,已经基本讲了。
标签:
原文地址:http://www.cnblogs.com/Leo_wl/p/4609338.html