做者 | 春哥大魔王
来源 | Serverless 公众号前端
在 SaaS 领域 Salesforce 是佼佼者,其 CRM 的概念已经扩展到了 Marketing、Sales、Service 等领域。那么 Salesforce 靠什么变成了这三个行业的解决方案呢?得益于 Salesforce 强大的 aPaaS 平台。java
ISV、内部实施、客户都可以从本身的维度基于 aPaaS 平台构建本身的行业,实现业务定制,甚至是行业定制。由于在此以前只有在 Sales 方向有专门的 SaaS 产品,而 Marketing 和 Service 都是由本身的 ISV 在各自行业的解决方案。因此 Salesforce 已经从一家 SaaS 公司变成了一家 aPaaS 平台公司了。python
搭建一个 aPaaS 平台是须要很长时间的,固然也能够基于一些公有云产品的 Serverless 方案实现现有系统的灵活性与扩展性,从而实现针对于不一样客户的定制。ios
Serverless 由两部分组成,Server 和 Less。golang
组合起来就是较少服务端干预的服务端解决方案。shell
与 Serverless 相对的是 Serverfull,比较下对应的概念可能更便于理解。编程
在 Serverfull 时代,研发交付流程通常有三个角色:RD,PM,QA。json
RD 根据 PM 的 PRD 进行功能开发,交付到 QA 进行测试,测试完成以后发布到服务器。由运维人员规划服务器规格、数量、机房部署、节点扩缩容等,这种更多由人力处理的时代就是 Serverfull 时代。axios
以后进入了 DevOps 时代。这个时代运维本身开发一套运维控制台,可让研发同窗在控制台上本身进行服务观测、数据查询、运维处理等,运维同窗的工做轻松了很多,这个阶段主要释放了运维同窗的人力。后端
而到了 Serverless 时代,这套运维控制台能力愈来愈丰富,能够实现按配置的自动扩缩容、性能监控、DevOps 流水线等,同时侵入到研发流程侧,好比自动发布流水线、编译打包、代码质量监测、灰度发布、弹性扩缩等流程基本不须要人力处理了,这就是 Serverless 时代。
相信你有过这样的经历,在一个 Web 界面上,左侧写代码,右侧展现执行效果。
以阿里云解决方案看下如何支持多语言架构:
抽象来讲,前端只须要将代码片断和编程语言的标识传给 Server 端便可,等待响应结果。Server 端能够针对于不一样编程语言进行 runtime 分类、预处理等工做。
不少人把 Serverless 看作是 FC(function compute:函数计算),使用函数计算,无需业务本身搭建 IT 基础设施,只须要编码并上传代码。函数计算会按需为你准备好计算资源,弹性、可靠地运行,并提供 trace、日志查询、监控告警等治理能力。
好比:
在 FC 中有服务和函数之分。一个服务能够包含多个函数。咱们能够用微服务理解,咱们经过 golang 或 java 搭建了一个微服务架构,而 FC 服务就是其中的类,FC 函数是类中的一个方法:
区别在于 Java 搭建的微服务只能运行 java 类代码,golang 的类只能运行 go 写的代码,而 FC 函数能够安装不一样语言的 runtime,支持运行不一样语言程序。
类比理解以后,咱们再看下如何调用 FC 的函数,通常的 FC 解决方案里面都有一个触发器的概念。好比 HTTP 触发器、对象存储触发器、日志服务触发器、定时任务触发器、CDN 触发器、消息队列触发器等。触发器是对于 FC 函数调用的抽象收口,好比 HTTP 触发器通常都类比网关的一个 http 请求事件,或是指定对象存储路径下上传了一个图片,这些触发事件的入口均可以是触发器。
触发器产生事件以后能够调用 FC 函数,函数执行的逻辑能够是下载一张图片或是注册一个用户。
这样从触发器到 FC 函数逻辑处理就是一个 FC 的生命周期了。
那么 FC 是如何实现高可用的呢?
其实每一个函数底层代码都是运行在一套 IaaS 平台上,使用 IaaS 资源,咱们能够为每一个函数设置运行代码时须要的内存配置便可,好比最小 128M,最大 3G 等。研发人员不须要关心代码运行在什么样的服务器上,不须要关心启动了多少函数实例支持当前场景,不须要关注背后的弹性扩缩问题,这些都被收敛在 FC 以后。
如图有两种高可用策略:
相似于线程池的方案。
那么 Serverless 如何提效呢?
有了服务以后就能够建立函数了,好比选择基于 http 请求的函数。
配置触发器,好比选择了 HTTP 触发器,而后在触发器上绑定函数名称,因为是 http 访问,能够选择访问的鉴权、认证方式,以及请求方式 POST or GET。
当函数建立好了以后,进入函数,能够看到描述、代码执行历史、触发器类型、日志查询页等。
若是是 HTTP 触发器,须要配置 http 触发路径。
能够看到就如前面介绍的那种,相似于类里面的一个函数,上下文请求会打到这里,直接执行。
Python 代码为例:
# -*- coding: utf-8 -*- import logging import urllib.parse import time import subprocess def handler(environ, start_response): context = environ['fc.context'] request_uri = environ['fc.request_uri'] for k, v in environ.items(): if k.startswith('HTTP_'): pass try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 # 获取用户传入的code request_body = environ['wsgi.input'].read(request_body_size) codeStr = urllib.parse.unquote(request_body.decode("GBK")) # 由于body里的对象里有code和input两个属性,这里分别获取用户code和用户输入 codeArr = codeStr.split('&') code = codeArr[0][5:] inputStr = codeArr[1][6:] # 将用户code保存为py文件,放/tmp目录下,以时间戳为文件名 fileName = '/tmp/' + str(int(time.time())) + '.py' f = open(fileName, "w") # 这里预置引入了time库 f.write('import time \r\n') f = open(fileName, "a") f.write(code) f.close() # 建立子进程,执行刚才保存的用户code py文件 p = subprocess.Popen("python " + fileName, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, encoding='utf-8') # 经过标准输入传入用户的input输入 if inputStr != '' : p.stdin.write(inputStr + "\n") p.stdin.flush() # 经过标准输出获取代码执行结果 r = p.stdout.read() status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [r.encode('UTF-8')]
流程以下:
前端调用 FC 函数:
整个过程只须要前端将代码传入到 FC 函数里面,整个 Server 端各个环节都不须要研发与运维同窗关心,体现了 Serverless 的精髓。
工做流能够用顺序、分支、并行等方式来编排任务执行,以后流程会按照设定好的步骤可靠地协调任务执行,跟踪每一个任务的状态切换,并在必要时执行定义的重试逻辑,确保流程顺利执行。
工做流流程经过记录日志和审计方式来监视工做流的执行,便于流程的诊断与调试。
系统灵活性与扩展性的核心是服务可编排,因此咱们须要作的是将现有系统内部用户但愿定制的功能进行梳理、拆分、抽离、结合 FC 提供的无状态能力,将这些功能点进行编排,实现业务流程的定制。
举个例子,好比餐饮场景下不一样商家能够配置不一样的支付方式,能够走微信支付、银联支付、支付宝支付。能够同时支持三家,也能够某一家,能够到付,也能够积分兑换等。若是没有一个好的配置化流程解决方案的话,系统中会出现大量硬编码规则判断条件,系统迭代疲于奔命,是个不可持续的过程。
有了 FC 搭建的工做流就能够很优雅地解决这种问题,好比规整流程以下:
上面的流程是用户侧的流程,接下来须要转换成程序侧的流程,经过约束的 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 的 FC 可实现灵活工做流。
流程如何触发的呢?
在用户选择完商品、填完地址以后,经过拉取商品、订单上下文,能够自动化触发流程了。
在微服务背景下,不少能力不是闭环在单体代码逻辑以内,不少时候是多个业务系统的链接,好比串联多个 OpenAPI 接口实现全流程:
如想使用流程引擎须要进行相关的备案鉴权:
@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; } }
startFNF 代码里面流程如何串联起来:
@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); }
再看下 fnfService.startFNF:
@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); }
前端如何调用?
在前端当点击选择商品和商家页面中的下一步后,经过 GET 方式调用 HTTP 协议的接口 /startFNF/{fnfname}/{execuname}/{input}。和上面的 Java 方法对应。
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) } }) }
先看下第一个 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
Serverless 工做流支持多个云服务集成,将其余服务做为任务步骤的执行单元。服务集成方式经过 FDL 表达式实现,在任务步骤中,可使 用resourceArn 来定义集成的目标服务,使用 pattern 定义集成模式。
在 resourceArn 中配置 /topics/generateInfo-fnf-demo-jiyuan/messages 信息,就是集成了 MNS 消息队列服务,当 generateInfo 节点触发后会向 generateInfo-fnf-demo-jiyuanTopic 中发送一条消息。消息的正文和参数在 serviceParams 对象中 zhi'd 指定。MessageBody 是消息正文,配置 $ 表示经过输入映射 inputMappings 产生消息正文。
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'
代码分五部分:
构建 Serverless 工做流 Client;
event 内的信息即接受到 TopicgenerateInfo-fnf-demo-jiyuan 中的消息内容,将其转换为 Json 对象;
判断什么商家使用什么样的支付方式组合,这里的示例比较简单粗暴,正常状况下,应该使用元数据配置的方式获取。好比在系统内有商家信息的配置功能,经过在界面上配置该商家支持哪些支付方式,造成元数据配置信息,提供查询接口,在这里进行查询;
调用 Java 服务暴露的接口,更新订单信息,主要是更新支付方式;
generateInfo-fnf-demo 函数配置了 MNS 触发器,当 TopicgenerateInfo-fnf-demo-jiyuan 有消息后就会触发执行 generateInfo-fnf-demo 函数。
接下来是 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 节点。
前端选择支付方式页面:
通过 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 发送用户选择的支付方式和金额。
paymentCombination 节点是一个路由节点,经过判断某个参数路由到不一样的节点,以 paymentMethod 做为判断条件:
- 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 节点:
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 函数:
# -*- 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 的 FC 实现的工做流,模拟构建了一个订单模块,规则包括:
在实际项目中,须要将可定制的部分抽象为元数据描述,须要有配置界面供运营或商家定制支付方式也就是元数据规则,而后先后端页面基于元数据信息展现相应的内容。
若是以后须要接入新的支付方式,只须要在 paymentCombination 路由节点中肯定好路由规则,以后增长对应的支付方式函数便可,经过增长元数据配置项,就能够在页面展现新加的支付方式,并路由到新的支付函数中。
通过整篇文章相信不少人对于 Serverless 的定义,以及如何基于现有的公有云系统的 Serverless 功能实现商业能力已经有了必定的了解,甚至基于此有实力的公司能够自研一套 Serverless 平台。固然思想是相同的,其实文中不少逻辑与理论不止适用于 Serverless,就是咱们平常基于微服务的平台化/中台化解决方案,均可以从中获取设计养分在工做中应用。