Serverless在SaaS领域的最佳实践

简介: 特别对于当下的经济环境,SaaS厂商要明白,不能再经过烧钱的方式,只关注在本身的用户数量上,而更多的要思考如何帮助客户下降成本、增长效率,因此须要将更多的精力放在本身产品的定制化能力上。html

做者:阿里云解决方案架构师 计缘前端

随着互联网人口红利逐渐减弱,基于流量的增加已经放缓,互联网行业迫切须要找到一片足以承载自身持续增加的新蓝海,产业互联网正是这一宏大背景下的新趋势。咱们看到互联网浪潮正在席卷传统行业,云计算、大数据、人工智能开始大规模融入到金融、制造、物流、零售、文娱、教育、医疗等行业的生产环节中,这种融合称为产业互联网。而在产业互联网中,有一块不可小觑的领域是SaaS领域,它是ToB赛道的中坚力量,好比CRM、HRM、费控系统、财务系统、协同办公等等。java

SaaS系统面临的挑战

在消费互联网时代,你们是搜索想要的东西,各个厂商在云计算、大数据、人工智能等技术基座之上创建流量最大化的服务与生态,基于海量内容分发与流量共享为逻辑构建系统。而到了产业互联网时代,供给关系发生了变化,你们是定制想要的东西,须要从供给与需求两侧出发进行双向建设,这个时候系统的灵活性和扩展性面临着史无前例的挑战,尤为是ToB的SaaS领域。ios

image.png

特别对于当下的经济环境,SaaS厂商要明白,不能再经过烧钱的方式,只关注在本身的用户数量上,而更多的要思考如何帮助客户下降成本、增长效率,因此须要将更多的精力放在本身产品的定制化能力上。编程

image.png

如何应对挑战

SaaS领域中的佼佼者Salesforce,将CRM的概念扩展到Marketing、Sales、Service,而这三块领域中只有Sales有专门的SaaS产品,其余两个领域都是各个ISV在不一样行业的行业解决方案,靠的是什么?毋庸置疑,是Salesforce强大的aPaaS平台。ISV、内部实施、客户都可以在各自维度经过aPaaS平台构建本身行业、本身领域的SaaS系统,创建完整的生态。因此在我看来,如今的Salesforce已经由一家SaaS公司升华为一家aPaaS平台公司了。这种演进的过程也印证了消费互联网和产业互联网的转换逻辑以及后者的核心诉求。json

然而不是全部SaaS公司都有财力和时间去孵化和打磨本身的aPaaS平台,但市场的变化、用户的诉求是实实在在存在的。若要生存,就要求变。这个变的核心就是可以让本身目前的SaaS系统变得灵活起来,相对建设困难的aPaaS平台,咱们其实能够选择轻量且有效的Serverless方案来提高现有系统的灵活性和可扩展性,从而实现用户不一样的定制需求。axios

Serverless工做流

在上一篇文章《资源成本双优化!看Serverless颠覆编程教育的创新实践》中,已经对Serverless的概念作过阐述了,而且也介绍了Serverless函数计算(FC)的概念和实践。这篇文章中介绍一下构建系统灵活性的核心要素服务编排——Serverless工做流。c#

Serverless 工做流是一个用来协调多个分布式任务执行的全托管云服务。在 Serverless工做流中,能够用顺序、分支、并行等方式来编排分布式任务,Serverless工做流会按照设定好的步骤可靠地协调任务执行,跟踪每一个任务的状态转换,并在必要时执行您定义的重试逻辑,以确保工做流顺利完成。Serverless工做流经过提供日志记录和审计来监视工做流的执行,能够轻松地诊断和调试应用。微信

下面这张图描述了Serverless工做流如何协调分布式任务,这些任务能够是函数、已集成云服务API、运行在虚拟机或容器上的程序。架构

image.png

看完Serverless工做流的介绍,你们可能已经多少有点思路了吧。系统灵活性和可扩展性的核心是服务可编排,不管是之前的BPM仍是如今的aPaaS。因此基于Serverless工做流重构SaaS系统灵活性方案的核心思路,是将系统内用户最但愿定制的功能进行梳理、拆分、抽离,再配合函数计算(FC)提供无状态的能力,经过Serverless工做流进行这些功能点的编排,从而实现不一样的业务流程。

经过函数计算FC和Serverless工做流搭建灵活的订餐模块

订餐场景相信你们都不会陌生,在家叫外卖或者在餐馆点餐,都涉及到这个场景。当下也有不少提供点餐系统的SaaS服务厂商,有不少不错的SaaS点餐系统。随着消费互联网向产业互联网转换,这些SaaS点餐系统面临的定制化的需求也愈来愈多,其中有一个需求是不一样的商家在支付时会显示不一样的支付方式,好比从A商家点餐后付款时显示支付宝、微信支付、银联支付,从B商家点餐后付款时显示支付宝、京东支付。忽然美团又冒出来了美团支付,此时B商家接了美团支付,那么从B商家点餐后付款时显示支付宝、京东支付、美团支付。诸如此类的定制化需求愈来愈多,这些SaaS产品若是没有PaaS平台,那么就会疲于不断的经过硬代码增长条件判断来实现不一样商家的需求,这显然不是一个可持续发展的模式。

那么咱们来看看经过函数计算FC和Serverless工做流如何优雅的解决这个问题。先来看看这个点餐流程:

image.png

经过Serverless工做流建立流程

首选我须要将上面用户侧的流程转变为程序侧的流程,此时就须要使用Serverless工做流来担任此任务了。
打开Serverless控制台,建立订餐流程,这里Serverless工做流使用流程定义语言FDL建立工做流,如何使用FDL建立工做流请参阅文档。流程图以下图所示:

image.png

FDL代码为:

version: v1beta1
type: flow
timeoutSeconds: 3600
steps:
  - type: task
    name: generateInfo
    timeoutSeconds: 300
    resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages
    pattern: waitForCallback
    inputMappings:
      - target: taskToken
        source: $context.task.token
      - target: products
        source: $input.products
      - target: supplier
        source: $input.supplier
      - target: address
        source: $input.address
      - target: orderNum
        source: $input.orderNum
      - target: type
        source: $context.step.name 
    outputMappings:
      - target: paymentcombination
        source: $local.paymentcombination
      - target: orderNum
        source: $local.orderNum
    serviceParams:
      MessageBody: $
      Priority: 1
    catch:
      - errors:
          - FnF.TaskTimeout
        goto: orderCanceled
  - type: task
    name: payment
    timeoutSeconds: 300
    resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages
    pattern: waitForCallback
    inputMappings:
      - target: taskToken
        source: $context.task.token
      - target: orderNum
        source: $local.orderNum
      - target: paymentcombination
        source: $local.paymentcombination
      - target: type
        source: $context.step.name 
    outputMappings:
      - target: paymentMethod
        source: $local.paymentMethod
      - target: orderNum
        source: $local.orderNum
      - target: price
        source: $local.price
      - target: taskToken
        source: $input.taskToken
    serviceParams:
      MessageBody: $
      Priority: 1
    catch:
      - errors:
          - FnF.TaskTimeout
        goto: orderCanceled
  - type: choice
    name: paymentCombination
    inputMappings:
      - target: orderNum
        source: $local.orderNum
      - target: paymentMethod
        source: $local.paymentMethod
      - target: price
        source: $local.price
      - target: taskToken
        source: $local.taskToken
    choices:
      - condition: $.paymentMethod == "zhifubao"
        steps:
          - type: task
            name: zhifubao
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo
            inputMappings:
              - target: price
                source: $input.price             
              - target: orderNum
                source: $input.orderNum 
              - target: paymentMethod
                source: $input.paymentMethod
              - target: taskToken
                source: $input.taskToken
      - condition: $.paymentMethod == "weixin"
        steps:
          - type: task
            name: weixin
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo
            inputMappings:
            - target: price
              source: $input.price             
            - target: orderNum
              source: $input.orderNum
            - target: paymentMethod
              source: $input.paymentMethod
            - target: taskToken
              source: $input.taskToken
      - condition: $.paymentMethod == "unionpay"
        steps:
          - type: task
            name: unionpay
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo
            inputMappings:
            - target: price
              source: $input.price             
            - target: orderNum
              source: $input.orderNum 
            - target: paymentMethod
              source: $input.paymentMethod
            - target: taskToken
              source: $input.taskToken
    default:
      goto: orderCanceled
  - type: task
    name: orderCompleted
    resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/orderCompleted
    end: true
  - type: task
    name: orderCanceled
    resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/cancerOrder

在解析整个流程以前,我先要说明的一点是,咱们不是彻底经过Serverless函数计算和Serverless工做流来搭建订餐模块,只是用它来解决灵活性的问题,因此这个示例的主体应用是Java编写的,而后结合了Serverless函数计算和Serverless工做流。下面咱们来详细解析这个流程。

启动流程

按常理,开始点餐时流程就应该启动了,因此在这个示例中,个人设计是当咱们选择完商品和商家、填完地址后启动流程:

image.png

这里咱们经过Serverless工做流提供的OpenAPI来启动流程。

image.png

Java启动流程

这个示例我使用Serverless工做流的Java SDK,首先在POM文件中添加依赖:

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>[4.3.2,5.0.0)</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-fnf</artifactId>
    <version>[1.0.0,5.0.0)</version>
</dependency>

而后建立初始化Java SDK的Config类:

@Configuration
public class FNFConfig {

    @Bean
    public IAcsClient createDefaultAcsClient(){
        DefaultProfile profile = DefaultProfile.getProfile(
                "cn-xxx",          _// 地域ID_
                "ak",      _// RAM 帐号的AccessKey ID_
                "sk"); _// RAM 帐号Access Key Secret_
        IAcsClient client = new DefaultAcsClient(profile);
        return client;
    }

}

再来看Controller中的startFNF方法,该方法暴露GET方式的接口,传入三个参数:

  1. fnfname:要启动的流程名称。
  2. execuname:流程启动后的流程实例名称。
  3. input:启动输入参数,好比业务参数。
@GetMapping("/startFNF/{fnfname}/{execuname}/{input}")
    public StartExecutionResponse startFNF(@PathVariable("fnfname") String fnfName,
                                           @PathVariable("execuname") String execuName,
                                           @PathVariable("input") String inputStr) throws ClientException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("fnfname", fnfName);
        jsonObject.put("execuname", execuName);
        jsonObject.put("input", inputStr);
        return fnfService.startFNF(jsonObject);
    }

再来看Service中的startFNF方法,该方法分两部分,第一个部分是启动流程,第二部分是建立订单对象,并模拟入库(示例中是放在Map里了):

@Override
    public StartExecutionResponse startFNF(JSONObject jsonObject) throws ClientException {
        StartExecutionRequest request = new StartExecutionRequest();
        String orderNum = jsonObject.getString("execuname");
        request.setFlowName(jsonObject.getString("fnfname"));
        request.setExecutionName(orderNum);
        request.setInput(jsonObject.getString("input"));

        JSONObject inputObj = jsonObject.getJSONObject("input");
        Order order = new Order();
        order.setOrderNum(orderNum);
        order.setAddress(inputObj.getString("address"));
        order.setProducts(inputObj.getString("products"));
        order.setSupplier(inputObj.getString("supplier"));
        orderMap.put(orderNum, order);

        return iAcsClient.getAcsResponse(request);
    }

启动流程时,流程名称和启动流程实例的名称是须要传入的参数,这里我将每次的订单编号做为启动流程的实例名称。至于Input,能够根据需求构造JSON字符串传入。这里我将商品、商家、地址、订单号构造了JSON字符串在流程启动时传入流程中。

另外,建立了这次订单的Order实例,并存在Map中,模拟入库,后续环节还会查询该订单实例更新订单属性。

VUE选择商品/商家页面

前端我使用VUE搭建,当点击选择商品和商家页面中的下一步后,经过GET方式调用HTTP协议的接口/startFNF/{fnfname}/{execuname}/{input}。和上面的Java方法对应。

  1. fnfname:要启动的流程名称。
  2. execuname:随机生成uuid,做为订单的编号,也做为启动流程实例的名称。
  3. input:将商品、商家、订单号、地址构建为JSON字符串传入流程。
submitOrder(){
                const orderNum = uuid.v1()
                this.$axios.$get('/startFNF/OrderDemo-Jiyuan/'+orderNum+'/{n' +
                    '  "products": "'+this.products+'",n' +
                    '  "supplier": "'+this.supplier+'",n' +
                    '  "orderNum": "'+orderNum+'",n' +
                    '  "address": "'+this.address+'"n' +
                    '}' ).then((response) => {
                    console.log(response)
                    if(response.message == "success"){
                        this.$router.push('/orderdemo/' + orderNum)
                    }
                })
            }

generateInfo节点

第一个节点generateInfo,先来看看FDL的含义:

- type: task
    name: generateInfo
    timeoutSeconds: 300
    resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages
    pattern: waitForCallback
    inputMappings:
      - target: taskToken
        source: $context.task.token
      - target: products
        source: $input.products
      - target: supplier
        source: $input.supplier
      - target: address
        source: $input.address
      - target: orderNum
        source: $input.orderNum
      - target: type
        source: $context.step.name 
    outputMappings:
      - target: paymentcombination
        source: $local.paymentcombination
      - target: orderNum
        source: $local.orderNum
    serviceParams:
      MessageBody: $
      Priority: 1
    catch:
      - errors:
          - FnF.TaskTimeout
        goto: orderCanceled
  1. name:节点名称。
  2. timeoutSeconds:超时时间。该节点等待的时长,超过期间后会跳转到goto分支指向的orderCanceled节点。
  3. pattern:设置为waitForCallback,表示须要等待确认。inputMappings:该节点入参。
  • taskToken:Serverless工做流自动生成的Token。
  • products:选择的商品。
  • supplier:选择的商家。
  • address:送餐地址。
  • orderNum:订单号。
  1. outputMappings:该节点的出参。
  • paymentcombination:该商家支持的支付方式。
  • orderNum:订单号。
  1. catch:捕获异常,跳转到其余分支。

这里resourceArn和serviceParams须要拿出来单独解释。Serverless工做流支持与多个云服务集成,即将其余服务做为任务步骤的执行单元。服务集成方式由FDL语言表达,在任务步骤中,可使用resourceArn来定义集成的目标服务,使用pattern定义集成模式。因此能够看到在resourceArn中配置acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages信息,即在generateInfo节点中集成了MNS消息队列服务,当generateInfo节点触发后会向generateInfo-fnf-demo-jiyuanTopic中发送一条消息。那么消息正文和参数则在serviceParams对象中指定。MessageBody是消息正文,配置$表示经过输入映射inputMappings产生消息正文。

看完第一个节点的示例,你们能够看到,在Serverless工做流中,节点之间的信息传递能够经过集成MNS发送消息来传递,也是使用比较普遍的方式之一。

generateInfo-fnf-demo函数

向generateInfo-fnf-demo-jiyuanTopic中发送的这条消息包含了商品信息、商家信息、地址、订单号,表示一个下订单流程的开始,既然有发消息,那么必然有接受消息进行后续处理。因此打开函数计算控制台,建立服务,在服务下建立名为generateInfo-fnf-demo的事件触发器函数,这里选择Python Runtime:

image.png

建立MNS触发器,选择监听generateInfo-fnf-demo-jiyuanTopic。

image.png

打开消息服务MNS控制台,建立generateInfo-fnf-demo-jiyuanTopic:

image.png

作好函数的准备工做,咱们来开始写代码:

_# -*- coding: utf-8 -*-_
import logging
import json
import time
import requests
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest
from aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest


def handler(event, context):
    _# 1. 构建Serverless工做流Client_
    region = "cn-hangzhou"
    account_id = "XXXX"
    ak_id = "XXX"
    ak_secret = "XXX"
    fnf_client = AcsClient(
        ak_id,
        ak_secret,
        region
    )
    logger = logging.getLogger()
    _# 2. event内的信息即接受到Topic generateInfo-fnf-demo-jiyuan中的消息内容,将其转换为Json对象_
    bodyJson = json.loads(event)
    logger.info("products:" + bodyJson["products"])
    logger.info("supplier:" + bodyJson["supplier"])
    logger.info("address:" + bodyJson["address"])
    logger.info("taskToken:" + bodyJson["taskToken"])
    supplier = bodyJson["supplier"]
    taskToken = bodyJson["taskToken"]
    orderNum = bodyJson["orderNum"]
    _# 3. 判断什么商家使用什么样的支付方式组合,这里的示例比较简单粗暴,正常状况下,应该使用元数据配置的方式获取_
    paymentcombination = ""
    if supplier == "haidilao":
        paymentcombination = "zhifubao,weixin"
    else:
        paymentcombination = "zhifubao,weixin,unionpay"

    _# 4. 调用Java服务暴露的接口,更新订单信息,主要是更新支付方式_
    url = "http://xx.xx.xx.xx:8080/setPaymentCombination/" + orderNum + "/" + paymentcombination + "/0"
    x = requests.get(url)

    _# 5. 给予generateInfo节点响应,并返回数据,这里返回了订单号和支付方式_
    output = "{"orderNum": "%s", "paymentcombination":"%s" " 
                         "}" % (orderNum, paymentcombination)
    request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()
    request.set_Output(output)
    request.set_TaskToken(taskToken)
    resp = fnf_client.do_action_with_exception(request)
    return 'hello world'

由于generateInfo-fnf-demo函数配置了MNS触发器,因此当TopicgenerateInfo-fnf-demo-jiyuan有消息后就会触发执行generateInfo-fnf-demo函数。

整个代码分五部分:

  1. 构建Serverless工做流Client。
  2. event内的信息即接受到TopicgenerateInfo-fnf-demo-jiyuan中的消息内容,将其转换为Json对象。
  3. 判断什么商家使用什么样的支付方式组合,这里的示例比较简单粗暴,正常状况下,应该使用元数据配置的方式获取。好比在系统内有商家信息的配置功能,经过在界面上配置该商家支持哪些支付方式,造成元数据配置信息,提供查询接口,在这里进行查询。
  4. 调用Java服务暴露的接口,更新订单信息,主要是更新支付方式。
  5. 给予generateInfo节点响应,并返回数据,这里返回了订单号和支付方式。由于该节点的pattern是waitForCallback,因此须要等待响应结果。

payment节点

咱们再来看第二个节点payment,先来看FDL代码:

- type: task
    name: payment
    timeoutSeconds: 300
    resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages
    pattern: waitForCallback
    inputMappings:
      - target: taskToken
        source: $context.task.token
      - target: orderNum
        source: $local.orderNum
      - target: paymentcombination
        source: $local.paymentcombination
      - target: type
        source: $context.step.name 
    outputMappings:
      - target: paymentMethod
        source: $local.paymentMethod
      - target: orderNum
        source: $local.orderNum
      - target: price
        source: $local.price
      - target: taskToken
        source: $input.taskToken
    serviceParams:
      MessageBody: $
      Priority: 1
    catch:
      - errors:
          - FnF.TaskTimeout
        goto: orderCanceled

当流程流转到payment节点后,意味着用户进入了支付页面。

image.png

这时payment节点会向MNS的Topicpayment-fnf-demo-jiyuan发送消息,会触发payment-fnf-demo函数。

payment-fnf-demo函数

payment-fnf-demo函数的建立方式和generateInfo-fnf-demo函数相似,这里再也不累赘。咱们直接来看代码:

_# -*- coding: utf-8 -*-_
import logging
import json
import os
import time
import logging
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkcore.client import AcsClient
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest
from aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest
from mns.account import Account  _# pip install aliyun-mns_
from mns.queue import *


def handler(event, context):
    logger = logging.getLogger()
    region = "xxx"
    account_id = "xxx"
    ak_id = "xxx"
    ak_secret = "xxx"
    mns_endpoint = "http://your_account_id.mns.cn-hangzhou.aliyuncs.com/"
    queue_name = "payment-queue-fnf-demo"
    my_account = Account(mns_endpoint, ak_id, ak_secret)
    my_queue = my_account.get_queue(queue_name)
    _# my_queue.set_encoding(False)_
    fnf_client = AcsClient(
        ak_id,
        ak_secret,
        region
    )
    eventJson = json.loads(event)

    isLoop = True
    while isLoop:
        try:
            recv_msg = my_queue.receive_message(30)
            isLoop = False
            _# body = json.loads(recv_msg.message_body)_
            logger.info("recv_msg.message_body:======================" + recv_msg.message_body)
            msgJson = json.loads(recv_msg.message_body)
            my_queue.delete_message(recv_msg.receipt_handle)
            _# orderCode = int(time.time())_
            task_token = eventJson["taskToken"]
            orderNum = eventJson["orderNum"]
            output = "{"orderNum": "%s", "paymentMethod": "%s", "price": "%s" " 
                         "}" % (orderNum, msgJson["paymentMethod"], msgJson["price"])
            request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()
            request.set_Output(output)
            request.set_TaskToken(task_token)
            resp = fnf_client.do_action_with_exception(request)
        except Exception as e:
            logger.info("new loop")
    return 'hello world'

该函数的核心思路是等待用户在支付页面选择某个支付方式确认支付。因此这里使用了MNS的队列来模拟等待。循环等待接收队列payment-queue-fnf-demo中的消息,当收到消息后将订单号和用户选择的具体支付方式以及金额返回给payment节点。

VUE选择支付方式页面

由于通过generateInfo节点后,该订单的支付方式信息已经有了,因此对于用户而言,当填完商品、商家、地址后,跳转到的页面就是该确认支付页面,而且包含了该商家支持的支付方式。

image.png

当进入该页面后,会请求Java服务暴露的接口,获取订单信息,根据支付方式在页面上显示不一样的支付方式。代码片断以下:

image.png

当用户选定某个支付方式点击提交订单按钮后,向payment-queue-fnf-demo队列发送消息,即通知payment-fnf-demo函数继续后续的逻辑。

这里我使用了一个HTTP触发器类型的函数,用于实现向MNS发消息的逻辑,paymentMethod-fnf-demo函数代码以下。

_# -*- coding: utf-8 -*-_

import logging
import urllib.parse
import json
from mns.account import Account  _# pip install aliyun-mns_
from mns.queue import *
HELLO_WORLD = b'Hello world!n'

def handler(environ, start_response):
    logger = logging.getLogger()  
    context = environ['fc.context']
    request_uri = environ['fc.request_uri']
    for k, v in environ.items():
      if k.startswith('HTTP_'):
        _# process custom request headers_
        pass
    try:        
        request_body_size = int(environ.get('CONTENT_LENGTH', 0))    
    except (ValueError):        
        request_body_size = 0   
    request_body = environ['wsgi.input'].read(request_body_size)  
    paymentMethod = urllib.parse.unquote(request_body.decode("GBK"))
    logger.info(paymentMethod)
    paymentMethodJson = json.loads(paymentMethod)

    region = "cn-xxx"
    account_id = "xxx"
    ak_id = "xxx"
    ak_secret = "xxx"
    mns_endpoint = "http://your_account_id.mns.cn-hangzhou.aliyuncs.com/"
    queue_name = "payment-queue-fnf-demo"
    my_account = Account(mns_endpoint, ak_id, ak_secret)
    my_queue = my_account.get_queue(queue_name)
    output = "{"paymentMethod": "%s", "price":"%s" " 
                         "}" % (paymentMethodJson["paymentMethod"], paymentMethodJson["price"])
    msg = Message(output)
    my_queue.send_message(msg)
    
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]

该函数的逻辑很简单,就是向MNS的队列payment-queue-fnf-demo发送用户选择的支付方式和金额。
VUE代码片断以下:

image.png

paymentCombination节点

paymentCombination节点是一个路由节点,经过判断某个参数路由到不一样的节点,这里天然使用paymentMethod做为判断条件。FDL代码以下:

- type: choice
    name: paymentCombination
    inputMappings:
      - target: orderNum
        source: $local.orderNum
      - target: paymentMethod
        source: $local.paymentMethod
      - target: price
        source: $local.price
      - target: taskToken
        source: $local.taskToken
    choices:
      - condition: $.paymentMethod == "zhifubao"
        steps:
          - type: task
            name: zhifubao
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo
            inputMappings:
              - target: price
                source: $input.price             
              - target: orderNum
                source: $input.orderNum 
              - target: paymentMethod
                source: $input.paymentMethod
              - target: taskToken
                source: $input.taskToken
      - condition: $.paymentMethod == "weixin"
        steps:
          - type: task
            name: weixin
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo
            inputMappings:
            - target: price
              source: $input.price             
            - target: orderNum
              source: $input.orderNum
            - target: paymentMethod
              source: $input.paymentMethod
            - target: taskToken
              source: $input.taskToken
      - condition: $.paymentMethod == "unionpay"
        steps:
          - type: task
            name: unionpay
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo
            inputMappings:
            - target: price
              source: $input.price             
            - target: orderNum
              source: $input.orderNum 
            - target: paymentMethod
              source: $input.paymentMethod
            - target: taskToken
              source: $input.taskToken
    default:
      goto: orderCanceled

这里的流程是,用户选择支付方式后,经过消息发送给payment-fnf-demo函数,而后将支付方式返回,因而流转到paymentCombination节点经过判断支付方式流转到具体处理支付逻辑的节点和函数。

zhifubao节点

咱们具体来看一个zhifubao节点:

choices:
      - condition: $.paymentMethod == "zhifubao"
        steps:
          - type: task
            name: zhifubao
            resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo
            inputMappings:
              - target: price
                source: $input.price             
              - target: orderNum
                source: $input.orderNum 
              - target: paymentMethod
                source: $input.paymentMethod
              - target: taskToken
                source: $input.taskToken

这个节点的resourceArn和以前两个节点的不一样,这里配置的是函数计算中函数的ARN,也就是说当流程流转到这个节点时会触发zhifubao-fnf-demo函数,该函数是一个事件触发函数,但不须要建立任何触发器。流程将订单金额、订单号、支付方式传给zhifubao-fnf-demo函数。

zhifubao-fnf-demo函数

如今咱们来看zhifubao-fnf-demo函数的代码:

_# -*- coding: utf-8 -*-_
import logging
import json
import requests
import urllib.parse
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest
from aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest


def handler(event, context):
  region = "cn-xxx"
  account_id = "xxx"
  ak_id = "xxx"
  ak_secret = "xxx"
  fnf_client = AcsClient(
    ak_id,
    ak_secret,
    region
  )
  logger = logging.getLogger()
  logger.info(event)
  bodyJson = json.loads(event)
  price = bodyJson["price"]
  taskToken = bodyJson["taskToken"]
  orderNum = bodyJson["orderNum"]
  paymentMethod = bodyJson["paymentMethod"]
  logger.info("price:" + price)
  newPrice = int(price) * 0.8
  logger.info("newPrice:" + str(newPrice))
  url = "http://xx.xx.xx.xx:8080/setPaymentCombination/" + orderNum + "/" + paymentMethod + "/" + str(newPrice)
  x = requests.get(url)

  return {"Status":"ok"}

示例中的代码逻辑很简单,接收到金额后,将金额打8折,而后将价格更新回订单。其余支付方式的节点和函数如法炮制,变动实现逻辑就能够。在这个示例中,微信支付打了5折,银联支付打7折。

完整流程

流程中的orderCompleted和orderCanceled节点没作什么逻辑,你们能够自行发挥,思路和以前的节点同样。因此完整的流程是这样:

image.png

从Serverless工做流中看到的节点流转是这样的:

image.png

总结

到此,咱们基于Serverless工做流和Serverless函数计算构建的订单模块示例就算完成了,在示例中,有两个点须要你们注意:

  1. 配置商家和支付方式的元数据规则。
  2. 确认支付页面的元数据规则。

由于在实际生产中,咱们须要将可定制的部分都抽象为元数据描述,须要有配置界面制定商家的支付方式即更新元数据规则,而后前端页面基于元数据信息展现相应的内容。

因此若是以后须要接入其余的支付方式,只需在paymentCombination路由节点中肯定好路由规则,而后增长对应的支付方式函数便可。经过增长元数据配置项,就能够在页面显示新加的支付方式,而且路由处处理新支付方式的函数中。

以上内容做为抛砖引玉之石,探索Serverless的应用场景,来解决SaaS厂商灵活性和扩展性的痛点。你们若是有任何疑问也能够加入钉钉群35712134来寻找答案,咱们不见不散!

原文连接本文为阿里云原创内容,未经容许不得转载。

相关文章
相关标签/搜索