简介: 特地对于当下的经济环境,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: v1beta1type: flowtimeoutSeconds: 3600steps:  - 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类:

@Configurationpublic 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 loggingimport jsonimport timeimport requestsfrom aliyunsdkcore.client import AcsClientfrom aliyunsdkcore.acs_exception.exceptions import ServerExceptionfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequestfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequestdef 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 loggingimport jsonimport osimport timeimport loggingfrom aliyunsdkcore.client import AcsClientfrom aliyunsdkcore.acs_exception.exceptions import ServerExceptionfrom aliyunsdkcore.client import AcsClientfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequestfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequestfrom 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 loggingimport urllib.parseimport jsonfrom 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 loggingimport jsonimport requestsimport urllib.parsefrom aliyunsdkcore.client import AcsClientfrom aliyunsdkcore.acs_exception.exceptions import ServerExceptionfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequestfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequestdef 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来寻找答案,咱们不见不散!

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