关于前端:Serverless在SaaS领域的最佳实践

32次阅读

共计 18834 个字符,预计需要花费 48 分钟才能阅读完成。

简介: 特地对于当下的经济环境,SaaS 厂商要明确,不能再通过烧钱的形式,只关注在本人的用户数量上,而更多的要思考如何帮忙客户降低成本、减少效率,所以须要将更多的精力放在本人产品的定制化能力上。

作者:阿里云解决方案架构师 计缘

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

SaaS 零碎面临的挑战

在生产互联网时代,大家是 搜寻 想要的货色,各个厂商在云计算、大数据、人工智能等技术基座之上建设流量最大化的服务与生态,基于海量内容散发与流量共享为逻辑构建零碎。而到了产业互联网时代,供应关系产生了变动,大家是 定制 想要的货色,须要从供应与需要两侧登程进行双向建设,这个时候零碎的灵活性和扩展性面临着前所未有的挑战,尤其是 ToB 的 SaaS 畛域。

特地对于当下的经济环境,SaaS 厂商要明确,不能再通过烧钱的形式,只关注在本人的用户数量上,而更多的要思考如何帮忙客户降低成本、减少效率,所以须要将更多的精力放在本人产品的定制化能力上。

如何应答挑战

SaaS 畛域中的佼佼者 Salesforce,将 CRM 的概念扩大到 Marketing、Sales、Service,而这三块畛域中只有 Sales 有专门的 SaaS 产品,其余两个畛域都是各个 ISV 在不同行业的行业解决方案,靠的是什么?毋庸置疑,是 Salesforce 弱小的 aPaaS 平台。ISV、外部施行、客户均能够在各自维度通过 aPaaS 平台构建本人行业、本人畛域的 SaaS 零碎,建设残缺的生态。所以在我看来,当初的 Salesforce 曾经由一家 SaaS 公司升华为一家 aPaaS 平台公司了。这种演进的过程也印证了生产互联网和产业互联网的转换逻辑以及后者的外围诉求。

然而不是所有 SaaS 公司都有财力和工夫去孵化和打磨本人的 aPaaS 平台,但市场的变动、用户的诉求是实实在在存在的。若要生存,就要求变。这个变的外围就是可能让本人目前的 SaaS 零碎变得灵便起来,绝对建设艰难的 aPaaS 平台,咱们其实能够抉择轻量且无效的 Serverless 计划来晋升现有零碎的灵活性和可扩展性,从而实现用户不同的定制需要。

Serverless 工作流

在上一篇文章《资源老本双优化!看 Serverless 颠覆编程教育的翻新实际》中,曾经对 Serverless 的概念做过论述了,并且也介绍了 Serverless 函数计算(FC)的概念和实际。这篇文章中介绍一下构建零碎灵活性的外围因素服务编排——Serverless 工作流。

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

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

看完 Serverless 工作流的介绍,大家可能曾经多少有点思路了吧。零碎灵活性和可扩展性的外围是服务可编排,无论是以前的 BPM 还是当初的 aPaaS。所以基于 Serverless 工作流重构 SaaS 零碎灵活性计划的外围思路,是将零碎内用户最心愿定制的性能进行梳理、拆分、抽离,再配合函数计算(FC)提供无状态的能力,通过 Serverless 工作流进行这些性能点的编排,从而实现不同的业务流程。

通过函数计算 FC 和 Serverless 工作流搭建灵便的订餐模块

订餐场景置信大家都不会生疏,在家叫外卖或者在餐馆点餐,都波及到这个场景。当下也有很多提供点餐零碎的 SaaS 服务厂商,有很多不错的 SaaS 点餐零碎。随着生产互联网向产业互联网转换,这些 SaaS 点餐零碎面临的定制化的需要也越来越多,其中有一个需要是不同的商家在领取时会显示不同的领取形式,比方从 A 商家点餐后付款时显示支付宝、微信领取、银联领取,从 B 商家点餐后付款时显示支付宝、京东领取。忽然美团又冒出来了美团领取,此时 B 商家接了美团领取,那么从 B 商家点餐后付款时显示支付宝、京东领取、美团领取。诸如此类的定制化需要越来越多,这些 SaaS 产品如果没有 PaaS 平台,那么就会疲于一直的通过硬代码减少条件判断来实现不同商家的需要,这显然不是一个可继续倒退的模式。

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

通过 Serverless 工作流创立流程

首选我须要将下面用户侧的流程转变为程序侧的流程,此时就须要应用 Serverless 工作流来负责此工作了。
关上 Serverless 控制台,创立订餐流程,这里 Serverless 工作流应用流程定义语言 FDL 创立工作流,如何应用 FDL 创立工作流请参阅文档。流程图如下图所示:

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 工作流。上面咱们来具体解析这个流程。

启动流程

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

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

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:

创立 MNS 触发器,抉择监听 generateInfo-fnf-demo-jiyuanTopic。

关上音讯服务 MNS 控制台,创立 generateInfo-fnf-demo-jiyuanTopic:

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

_# -*- 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 节点后,意味着用户进入了领取页面。

这时 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 节点后,该订单的领取形式信息曾经有了,所以对于用户而言,当填完商品、商家、地址后,跳转到的页面就是该确认领取页面,并且蕴含了该商家反对的领取形式。

当进入该页面后,会申请 Java 服务裸露的接口,获取订单信息,依据领取形式在页面上显示不同的领取形式。代码片段如下:

当用户选定某个领取形式点击提交订单按钮后,向 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 代码片段如下:

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 节点没做什么逻辑,大家能够自行施展,思路和之前的节点一样。所以残缺的流程是这样:

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

总结

到此,咱们基于 Serverless 工作流和 Serverless 函数计算构建的订单模块示例就算实现了,在示例中,有两个点须要大家留神:

  1. 配置商家和领取形式的元数据规定。
  2. 确认领取页面的元数据规定。

因为在理论生产中,咱们须要将可定制的局部都形象为元数据形容,须要有配置界面制订商家的领取形式即更新元数据规定,而后前端页面基于元数据信息展现相应的内容。

所以如果之后须要接入其余的领取形式,只需在 paymentCombination 路由节点中确定好路由规定,而后减少对应的领取形式函数即可。通过减少元数据配置项,就能够在页面显示新加的领取形式,并且路由到解决新领取形式的函数中。

以上内容作为抛砖引玉之石,摸索 Serverless 的利用场景,来解决 SaaS 厂商灵活性和扩展性的痛点。大家如果有任何疑难也能够退出钉钉群 35712134 来寻找答案,咱们不见不散!

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

正文完
 0