业务系统数据库设计经验总结(二)

  从一个生产环境的退款bug说起。

  由于生产环境中第三方支付的一些规则不熟悉(第三方支付环境是在每天凌晨进行结账,所以用户的资金池里的金额会被清0,此时用户退款时会提示余额不足无法退款),所以用户在凌晨进行退款时可能会失败。这样一来,系统中日积月累会出现一批由于退款失败而未结算订单。未结算的订单需要再次进行退款处理,此时按照系统的设计,再次用原先的退款业务单号进行退款,但是到此时才发现,第三方支付系统中,对于已经失败的退款订单,退款业务订单号必须不相同,如果用相同的退款订单号进行处理,第三方支付系统是不接受的。

  这里经过了一系列的折腾。包含中途进行退款业务订单的修改,批量处理后又发现了线上的一些bug,又对业务退款金额和第三方支付系统的订单金额进行比对,再进行还原处理......总之,这是一个很折腾的过程。

  这里需要提一个教训。如果系统开发完成后,完全没有办法在测试环境进行测试,而业务进度又非常的紧急,不得不在生产环境进行业务处理时,那么最好是一点一点进行功能放开。比如批量处理时,先进行1单,或者极少的批量订单的处理;再比如一个很复杂的业务功能,那么我们可以先限定指定的用户或者极少量的用户能够触发这个功能,相当于在线上对这个功能进行极少数的线上测试,待没有问题时,再进行批量的处理。否则大规模出了bug,就是一个灾难。

  回到刚才的这个问题。面对这样的问题,我们要知道,第三方支付系统接入时,它的规则和内部问题我们不太可能一次了解的非常清楚,这样带来的设计层面的改动是不可避免的,为了降低问题出现时我们系统的改动复杂度,我们应该怎么办呢?

  在这样的场景中,我们进行业务设计时,需要遵循一个大的原则,或者说,在设计此类模型时,我们需要考虑双方的规则与业务字段,尽量把两个系统的耦合程度降到最低。在我们自己的系统中,我们需要将业务和数据承载在我们的退款模型(表)上,我们系统内部所有退款业务的查询或者相关的操作,都是面向这个退款模型进行设计和开发;但是对外部即第三方支付系统的一些规则和数据,我们需要承载在另一个第三方退款模型上,这个模型主要针对第三方支付系统的交互,如调用退款接口,退款成功之后的回调等等。在此基础上,我们只需要再增加一个内部系统退款模型和外部退款模型的关系表,来映射两者的关系。

  此时,我们可以使用一个接口,从我们系统的业务功能点出发,来定义一个退款业务接口。对于不同的第三方支付系统接入时,我们定义不同的实现类,我们在这些实现类中,实现退款业务的接口,所有的方法都是基于各自的外部支付系统接入API,处理好各自的功能,以及各自的外部退款模型与我们系统内部退款模型的关系。在系统内部涉及到退款业务时,我们只需要获取到对应退款业务实现的Bean,调用对应的方法就OK。

  这样一来,我们基本做到了内部系统的退款处理逻辑与外部系统的退款实现的隔离,当再接入其它的第三方支付系统进行退款处理时,那么我们可以很方便的进行处理。当某一个支付系统的功能和接口发生变化时,我们可以花费最小必要的代价,对我们系统进行升级维护,而不必大动干戈地对内部业务逻辑进行大量修改。这样的处理其实很像JDBC的包装,各个厂家提供不同的驱动,不同的内部处理,但是都实现了一套统一的标准,我们使用的时候只需要调用对应的JDBC实体,即可使用对应的功能。这是一种比较通用的设计思路,在系统与外部进行交互时,隔离内外,分别处理是一个应该在需求定义时就考虑进来的系统设计方向。