Square Enix Boutique / E-commerce

image

Year : 2011.

Function : Project Manager @ SUPERGAZOL.

前端计算规则联动引擎

这个方案是之前交易平台重构的时候重新设计的,用于解决目前购物车确认下单里,复杂交易金额计算规则带来的维护问题。

目前淘宝的购物车确认下单金额计算包含了各个单品的金额总和,运费平台,优惠平台,商城积分等几个大部分,而其中每个部分内部又有自身独立的运算。比如运费还包含快递,邮政和COD(货到付款),快递的计算规则由各个卖家设定的运费模版决定,不同的商品有不同的运费模版,这样总运费就不是简单的商品*快递费了;COD里除了快递的运费模版之外,还需要对最终运费进行取整,这个取整规则又根据不同的价格区间而不同,根据不同的COD运费模版而不同。再比如优惠平台,包括单品优惠,店铺优惠和跨店优惠(活动促销)等,这些优惠信息都跟卖家相关,统一由优惠系统集中提供,每次修改数量,收货地址的时候影响了原始总价格,然后就需要重新计算优惠金额,从而影响了最终实际价格。

上面仅仅是最简单的列举了目前购物车确认下单里的计算逻辑。前端在页面需要实时的根据用户的修改作出计算,告诉用户当前的实际付款金额。这样复杂的金额计算规则,是整个购物系统经过好几年一步一步发展过来的。而以往的前端代码里,根据业务的变化,修改具体的计算代码,一步一步地变脏,变乱。代码的强烈耦合,导致有些前端无从了解具体业务,出现了直接根据DOM结构来计算金额的窘境。

我们经常讲代码是反映业务逻辑的,而不需要太多的注释来解释。从这个角度出发,要求我们更进一步的从代码的角度理解和抽象业务逻辑。从前端的角度,我们将每个具体的计算逻辑抽象为以下几点:

  1. 计算公式(计算逻辑)
  2. 公式所需要的变量
  3. 公式代入具体变量后的值(计算结果)

这样讲似乎看不出跟具体业务的关系,我们拿商品数量这个简单的例子来解释下:

  1. 计算公式:商品价格*商品数量
  2. 公式所需要的变量:商品价格:页面某个Tag;商品数量:输入框
  3. 公式代入具体变量后的值:算出来后传递出去。

具体到代码层面,还需要再进一步提出问题:

一、计算公式用什么方式描述?

可以采用原生的JS代码来描述具体的逻辑,因为维护这段代码的都是前端,能够理解。

二、变量通过什么方式传递?变量的类型是什么?

另外,因为我们计算的过程都是数字,所以为了简化,我们假设所有的变量都是数字。

三、计算结果通过什么方式传出?通过什么方式接收?

我们都知道观察者模式(自定义事件)最适合拿来做逻辑隔离(解耦),这样满足了我们不希望每个业务混杂在一起的初衷,每个业务规则可以只关心自己的计算,然后通过自定义事件接收变量;另外一方面,有些计算数据可能是等待一个异步结果,比如优惠部分的结果都需要等待Ajax的返回,那可以把这部分trigger的代码放到异步回调里。伪代码如下:

quantityPrice.bind('inputChange', function(e) {
  this.trigger('quantityPriceChange', price * e.quantity);
})

计算完成,通过trigger将结果抛出,抛出之后就等待其他监听方来接收了,自己也再不用关心自己被谁依赖了。

通过这三个问题,我们确定了一个基本的计算规则怎么和其他规则进行交互,以及交互过程中怎么传递数据。接下来,我们再进一步。我们知道所有规则都是通过自定义事件进行接收参数和抛出计算结果,那为何不将这一步再封装一下,从业务的角度重新设计一下API。

这里,我们就可以将这个计算规则独立抽象为一个类,这个类包装了特定规则的计算方法,也声明了该规则依赖了哪些变量等。我们将初始化参数设计为:

new Rule('quantityPrice', function(quantity, price) {
  return quantity * price
}, { parents: ['quantity', 'price'] })

这个初始化过程就封装了上述自定义事件绑定的过程。然后也声明了该规则依赖了商品价格和数量这两个变量。所有的计算规则都可以通过这个简单的API来描述。

进一步,我们追溯到quantityPrice的上游price,因为pricequantity这两个已经没有上游规则,这里我们再独立一个入口,提供给最顶的变量使用:

new Rule('price', function(e) {
  return parseInt(e.price, 10)
}, { vars: {price: $('#price').text() } })

有些时候,我们还遇到一些会动态变化的变量,比如商品数量,是在用户操作后才变化的,这时候,我们还需要一个手动触发更新的接口:

var quantityRule = new Rule('quantity', function(e) {
  return parseInt(e.quantity, 10)
})

我们通过update方法来传递特定的变量,同时手动触发计算:

quantityRule.update({ quantity: $('#quantity').val() })

这个方法就放在具体的input.onChange事件绑定即可。合并这两步,我们实际上还可以将quantityPrice这个规则简化为:

var quantity = new Rule('quantity', function(price, e) {
  return e.quantity * price
}, { parents: ['price'] })

input.bind('change', function() {
  quantity.update({quantity: $(this).val() })
})

在这个定义里,我们在声明parents: ['price']的时候,自动帮quantity这个对象监听了price的变动,update之后,又自动做了一步trigger('quantity'),即相当于:

Rule.bind('price', function(e) {
  quantity.caculate(e.result, { quantity: this.vars.quantity })
  Rule.trigger('quantity', {result: this.result})
})

这里出现了一个Rule执行ontrigger。传统的观察者模式里,下游规则要监听上游规则,需要把下游规则的代码写到上游规则的绑定代码里,这样又违背了我们解耦的初衷,所以采用事件中心的概念,集中一个对象来执行监听和广播。

将这些独立的节点联合起来之后,我们可以看到一个完整的运算网络:

这里我们已经完成了大部分工作,实现了具体业务的隔离,也重新调整了业务代码之间交互的方式,从而达到了业务解耦的目的。解耦之后,我们可以对每个规则独立调优,分配给不同的人员负责,独立文档,独立单元测试,等等。

前面的规则,都是设计为单个规则,单个计算的,而遇到同样的规则使用多次的时候,就乱套了。即,以上设计无法用于多实例的场景。我们知道,只要拿到了实例,那是很容易进行绑定和通信的,但是拿到实例意味着监听方不仅需要知道上游规则的名称,还需要得到上游规则的对象实例,意味着两个规则已经形成强耦合,又违背了我们不希望两个规则耦合的初衷。

在事件中心的设计里,事件监听和触发都是一个对象,我们采用另外的方式来实现实例管理:对事件进行分组,用事件名来描述实例,即触发和监听的时候,采用这种事件名:

Rule.trigger('{groupName}:{ruleName}', result)

这样,监听方就需要对’groupName’进行管理,维护上游传下来的分组信息,形成一个自定义的变量表,从而达到一个对象管理多个实例的目的。

Rule.bind('{groupName}:{ruleName}', function(e) {
    self.vars[e.groupName] = e.result;
})

而且,在计算的时候,也需要选择正确的分组变量,进行计算。

self.vars[e.groupName] * price

整个规则对象可以整理为这样:

每个规则就像一个多入口多出口的管道一样,自由组装,从而形成一个复杂的多实例联动网络。

这样的设计,对于整个计算过程而言是一个整体,我们可以统一对整个过程做集中优化,比如运算缓存。

在上游的计算结果通知下游的过程中,每次计算都会被触发,这样对于修改数量这个很顶级的手动操作而言,可能会导致相当多的下游重复计算。这时候我们可以在引擎通知下游的时候,做一下运算缓存,如果当前计算结果跟上一次是一样的,那么就没有必要重复通知了,因为对于下一个计算规则,所有变量都是一样的,再算一次没有任何意义。(这个思路来源于JavaScript Memorization)

if (this.cache[e.targetGroup] !== this.result)
  Rule.trigger('{groupName}:{ruleName}', this.result)

这样优化之后,整体重复运算过程少了至少1/3。

总的来说,这个框架的具体实现可能只适用于电子商务相关的金额计算过程抽象,在重构迁移完成之后对每个计算过程做优化,还是很简单的事情,而且单元测试也很容易保证。

比如COD的运算,基本上就是把运算方法迁移过来,然后直接写多一个规则调用这个方法,调整一下传参的方式即可,原来的单元测试代码全部可以保留。后期在优化的时候,对每个规则都写测试用例,规则分散个各个业务模块里,只要页面初始化就会加入,分工也简单了很多,每个人负责好自己的业务模块,写好自己的规则代码和测试代码,规则之间联动的事情,交给联动引擎就ok了。

自动事件绑定和基于文本的实例管理,这两者还都不容易把握,整个框架开发过程中花了很大心思来加强这两部分的稳定性。相比具体的实现,这两个模型倒是可以借鉴用到其他地方去。

If your business desires my respect, you should get on tumblr.

Also, a main website designed after 2002 is helpful.

The Australian Retailers Association Support's E-Commerce?

Say what? No — it can’t be true! The Australian Retailers Association (ARA) are one of the key players lobbying the Australian Government to investigate the fairness of online retailing. Given their primary focus is to “promote and protect retailers”, surely they wouldn’t support E-Commerce?

Excuse my ignorance, but it’s true — hurrah for E-Commerce. According to Ragtrader, the ARA hosted the first in a series ‘Engage in E-tail’ seminars for retailers early this week, attracting over 150 retailers. Well, we hate to boast, but dahhh, that’s why we created an E-Commerce platform dedicated to fashion designers and boutiques alike.

Thought we’d share this with you to keep your eye on the money — literally.

x

p.s. Note their mention of Dhini not once, but twice in the one newsletter. Big congrats to Dhini on her collaboration with Sportsgirl.

image

Agenzia di Grafica ad Arezzo - Realizzazione siti web


image


Agenzia grafica e Studio Grafico ad Arezzo.
Realizzazione siti web ad Arezzo e Cortona, Creazione siti internet, blog e portali a Montevarchi e in Casentino. Web Agency ad Arezzo che sviluppa siti web con cms Joomla e Wordpress. Realizzazione e commerce, personalizzazione shop online e posizionamento sito web nei motori di ricerca. Agenzia di Pubblicità e comunicazione ad Arezzo con cartelli stradali, Manifesti, Ape Vela, Insegne. Realizzazione servizi fotografici per modelle e foto professionali per Agriturismo, Ville, Casa Vacanze e stille life a Gioielli e Bigiotteria. Personalizzazione Negozio Ebay e Grafica personalizzata per Fan page di Facebook. Creazione grafica per brochure, Depliant e impaginazione cataloghi. Agenzia di grafica Pubblicitaria e Studio Grafico ad Arezzo

Listen

Every time you buy a Kindle Fire from Amazon, they lose money.  But they have a genius way of making it, and a lot more back.  The report from Planet Money.

E Store- My wardrobe on sale coming soon.

For those of you who dont know… I have been working on a small project to open up my own e-commerce store. For the sole purpose of selling off my wardrobe, my enormous mound of clothes which is taking up half my house! (husband not happy :( )

I am going to launch it tomorrow… so watch this space!

e.COMMERCE Best Practices

uxdesign.smashingmagazine.com

Know what you need to know when you need to know it. (Read: understand your customer before you try to sell them something). A great article from Smashing Magazine.

Getting paid to travel...

ytb.essenceexplores.com

Need extra cash? There’s a better way to earn more money than taking on a second job. Stop putting yourself through that and get back to living your life. We can show you a flexible plan perfect for part-time, full-time or any-time.


Let’s 

face it. The job market is tough right now, and job security is a thing of the past. Bottom line: There’s never been a better time to get involved with our company, in a fun and exciting industry that offers a plan flexible enough to fit your needs.

Loading more posts...