Skip to content

Latest commit

 

History

History
111 lines (75 loc) · 9.17 KB

emall-3-pay-the-bill.md

File metadata and controls

111 lines (75 loc) · 9.17 KB

对订单进行支付

有了订单账和应收信息,我们就可以交费了。为了能更多的演示 Nature 的特性,让我们故意虚构一些复杂的情景。我们假设用户的每张银行卡里的钱都不足以全额支付这笔订单,但是三张卡加起来是可以的。

记录每笔支付数据

我们需要支付系统告诉 Nature 用户支付的每一笔费用,为此我们需要定义一个支付单 Meta:

INSERT INTO meta
(meta_type, meta_key, description, version, states, fields, config)
VALUES('B', 'finance/payment', 'order payment', 1, '', '', '');

定义了 Meta 后我们就可以向 Nature 输入数据了。输入数据的代码请参考:nature-demo::emall::finance::pay。需要说明的是我们这里用到了Instance.sys_context属性,如下:

sys_context.insert("target.id".to_string(), format!("{}", id));

我们在里面放置了一个 target.id,其值为16进制的订单ID。其作用我们稍后讲。先让我们来看一下demo的运行效果。运行:

nature.exe
cargo.exe test --color=always --package nature-demo --lib emall::emall_test

之后我们便可以在 instance 数据表里的看到下面的数据:

ins_key content sys_context
B:finance/payment:1|85fcf20d28c053ac2d3103d1759cf123| {"order":4665262802592301254545277299928466637,"from_account":"b","paid":200,"pay_time":1589670980281} {"target.id":"3827f37003127855b32ea022daa04cd"}
B:finance/payment:1|df0d1867b9564ab3963dd8546aefec38| {"order":4665262802592301254545277299928466637,"from_account":"c","paid":700,"pay_time":1589670980286} {"target.id":"3827f37003127855b32ea022daa04cd"}
B:finance/payment:1|e18330eb534abe924a3d03760df3e90c| {"order":4665262802592301254545277299928466637,"from_account":"a","paid":100,"pay_time":1589670980275} {"target.id":"3827f37003127855b32ea022daa04cd"}

除了已经接触到的 ins_keycontent外,这里有出现了一个 sys_context 字段,里面放置了上面我们提到的 target.id数据。

在demo 示例代码中,我们故意将第二笔支付重新输入了一遍,以验证我们是否可以少交点钱,结果很好,并没有发生糟糕的事情。

将支付数据关联到订单账上

接下来我们就需要将这些支付数据记录到订单账上,来完成订单的支付。在此之前我们需要先建立支付单和订单账的关联关系。

-- payment --> orderAccount
INSERT INTO relation
(from_meta, to_meta, settings)
VALUES('B:finance/payment:1', 'B:finance/orderAccount:1', '{"executor":{"protocol":"localRust","url":"nature_demo:pay_count"}}');

关系做好了,但我们如何将这三笔不同的账记到同一个订单账上呢?有人会说支付单记录的订单号不就是订单账的号吗,没错,但 Nature 是不理解 Instance.content中的内容的。但 Nature 却可以理解 Instance.sys_context 中的内容,所以这就是为什么在里面放置 target.id 属性的原因了。有了 target.id Nature 就可以找到要操作的订单账了。

  • Nature 要点:订单账是状态数据,Nature 对状态数据有特殊的处理。在将支付单数据提交执行器pay_count)处理前,Nature 便会将orderAccount 的上一版本查出来一并给执行器(pay_count),而这个查询所需要的ID就来源于上面的 target.id

有关pay_count是如何工作的请自行查看示例代码。现在我们可以验证一下效果了。运行:

nature.exe
cargo.exe test --color=always --package nature-demo --lib emall::emall_test

之后我们便会发现 instance 数据表产生了下面的数据。

ins_key content states state_version from_key
B:finance/orderAccount:1|3827f37003127855b32ea022daa04cd| {"receivable":1000,"total_paid":0,"last_paid":0,"reason":"NewOrder","diff":-1000} ["unpaid"] 1 B:sale/order:1|3827f37003127855b32ea022daa04cd||0
B:finance/orderAccount:1|3827f37003127855b32ea022daa04cd| {"receivable":1000,"total_paid":100,"last_paid":100,"reason":"Pay","diff":-900} ["partial"] 2 B:finance/payment:1|e18330eb534abe924a3d03760df3e90c||0
B:finance/orderAccount:1|3827f37003127855b32ea022daa04cd| {"receivable":1000,"total_paid":300,"last_paid":200,"reason":"Pay","diff":-700} ["partial"] 3 B:finance/payment:1|85fcf20d28c053ac2d3103d1759cf123||0
B:finance/orderAccount:1|3827f37003127855b32ea022daa04cd| {"receivable":1000,"total_paid":1000,"last_paid":700,"reason":"Pay","diff":0} ["paid"] 4 B:finance/payment:1|df0d1867b9564ab3963dd8546aefec38||0

同一个ID的orderAccount一共有4条数据,第一条是创建订单时产生的,其它3条是支付产生的。在第4笔我们欣喜的发现 states 变为 “paid” 了,我们支付成功了。

  • Nature 要点:传统处理方式一般是采用update的方式将新状态覆盖到旧状态上,而要跟踪这些变化则需要额外的措施来保障,复杂度较高。而 Nature 通过增加版本号的方式来处理这个问题,Nature 绝不修改、删除数据,这样所有的数据,所有的改变都可以非常容易的被追溯
  • Nature 要点:Nature 对互斥状态支持的很好,你无需先删除一个状态再增加一个状态,如果你输入一个新的状态,Nature 会自动替换掉与之互斥的其它状态。
  • Nature 要点:如果你查询 Nature 的输出日志,你会看到重复提交的数据被忽略掉了,并不影响结果的正确性。
  • Nature 要点:我们几乎是同一时间提交了多笔支付数据,你会在 Nature 的输出日志上看到 “conflict” 字眼。这说明 Nature 为你解决了并发冲突问题,避免了脏数据的提交,而这一切对程序员来讲是无感知的。
  • Nature 要点:Nature 已经向你展示了不同业务事件控制状态的能力,而无需高难度的编程。由上面两个要点来看,Nature 大大降低系统的技术风险,同时也降低了程序员被罚款的风险:),还有借助 Nature 普通程序员可以挑战高级程序员的工作了。

是时候设置订单的状态了

订单账已经齐活了,接下来我们要将这个好消息告诉订单(状态),先建立一个关系:

-- orderAccount --> orderState
INSERT INTO relation
(from_meta, to_meta, settings)
VALUES('B:finance/orderAccount:1', 'B:sale/orderState:1', '{"selector":{"state_all":["paid"]},"target":{"state_add":["paid"]}}');

很高兴这里没有见到executor,也就是说我们又可以省去编码工作了,但我们还是要费点脑筋学点新东西。

  • selector.state_all:
    • selector是选择过滤器,只有符合条件的上游数据才可以进行关系处理。
    • state_all 是 selector 的一个条件,意思是上游状态数据必须包含所有我指定的状态。请参考使用 Relation
  • Nature 要点:Nature 提供了对上游状态数据进行选择的能力,可以通过非编程的方式来精细化控制执行器的输入。

在本示例里只有订单账的第4条数据可以满足这个条件,其它3条都不能满足。这一点可以通过下面的 from_key 来见证,最后面的4就是版本为4的订单账。

ins_key states state_version from_key
B:sale/orderState:1|3827f37003127855b32ea022daa04cd| ["paid"] 2 B:finance/orderAccount:1|3827f37003127855b32ea022daa04cd||4

Nature 幕后为你做了什么

  • Nature 要点:回过头来我们再看一下这一小节的内容,我们从输入支付数据到订单账再到订单状态,我们串接了三个节点,而Nature 可以让你无限度的串接,来满足你庞大的业务体系。
  • Nature 要点:Nature 不只是将业务点串成线,而且可以多个业务线交织成网,以一种即时可见的方式让你洞察业务布局的合理性。Nature 用足够短和足够通用的关系,来构建强大且灵活的业务系统。
  • Nature 要点:在本章节的Demo示例中我们大约写了100行的代码,完成了这个复杂的业务逻辑。包含并发,状态冲突控制,重试策略等,在传统开发模式下我们需要写多少代码呢?