隨着互聯網人口紅利逐漸減弱,基於流量的增加已經放緩,互聯網行業迫切須要找到一片足以承載自身持續增加的新藍海,產業互聯網正是這一宏大背景下的新趨勢。咱們看到互聯網浪潮正在席捲傳統行業,雲計算、大數據、人工智能開始大規模融入到金融、製造、物流、零售、文娛、教育、醫療等行業的生產環節中,這種融合稱爲產業互聯網。而在產業互聯網中,有一塊不可小覷的領域是 SaaS 領域,它是 ToB 賽道的中間力量,好比 CRM、HRM、費控系統、財務系統、協同辦公等等。前端
SaaS 系統面臨的挑戰
在消費互聯網時代,你們是搜索想要的東西,各個廠商在雲計算、大數據、人工智能等技術基座之上創建流量最大化的服務與生態,基於海量內容分發與流量共享爲邏輯構建系統。而到了產業互聯網時代,供給關係發生了變化,你們是定製想要的東西,須要從供給與需求兩側出發進行雙向建設,這個時候系統的靈活性和擴展性面臨着史無前例的挑戰,尤爲是 ToB 的 SaaS 領域。java
特別是對於當下的經濟環境,SaaS 廠商要明白,不能再經過燒錢的方式,只關注在本身的用戶數量上,而更多的要思考如何幫助客戶下降成本、增長效率,因此須要將更多的精力放在本身產品的定製化能力上。ios
如何應對挑戰
SaaS 領域中的佼佼者 Salesforce,將 CRM 的概念擴展到 Marketing、Sales、Service,而這三塊領域中只有 Sales 有專門的 SaaS 產品,其餘兩個領域都是各個 ISV 在不一樣行業的行業解決方案,靠的是什麼?毋庸置疑,是 Salesforce 強大的 aPaaS 平臺。ISV、內部實施、客戶都可以在各自維度經過 aPaaS 平臺構建本身行業、本身領域的 SaaS 系統,創建完整的生態。因此在我看來,如今的 Salesforce 已經由一家 SaaS 公司昇華爲一家 aPaaS 平臺公司了。這種演進的過程也印證了消費互聯網和產業互聯網的轉換邏輯以及後者的核心訴求。編程
然而不是全部 SaaS 公司都有財力和時間去孵化和打磨本身的 aPaaS 平臺,但市場的變化、用戶的訴求是實實在在存在的。若要生存,就要求變。這個變的核心就是可以讓本身目前的 SaaS 系統變得靈活起來,相對建設困難的 aPaaS 平臺,咱們其實能夠選擇輕量且有效的 Serverless 方案來提高現有系統的靈活性和可擴展性,從而實現用戶不一樣的定製需求。json
Serverless 工做流
在上一篇文章《資源成本雙優化!看 Serverless 顛覆編程教育的創新實踐》中,已經對 Serverless 的概念作過闡述了,而且也介紹了 Serverless 函數計算(FC)的概念和實踐。這篇文章中介紹一下構建系統靈活性的核心要素服務編排—— Serverless 工做流。axios
Serverless 工做流是一個用來協調多個分佈式任務執行的全託管雲服務。在 Serverless工做流中,能夠用順序、分支、並行等方式來編排分佈式任務,Serverless 工做流會按照設定好的步驟可靠地協調任務執行,跟蹤每一個任務的狀態轉換,並在必要時執行您定義的重試邏輯,以確保工做流順利完成。Serverless 工做流經過提供日誌記錄和審計來監視工做流的執行,能夠輕鬆地診斷和調試應用。微信
下面這張圖描述了 Serverless 工做流如何協調分佈式任務,這些任務能夠是函數、已集成雲服務 API、運行在虛擬機或容器上的程序。app
看完 Serverless 工做流的介紹,你們可能已經多少有點思路了吧。系統靈活性和可擴展性的核心是服務可編排,不管是之前的 BPM 仍是如今的 aPaaS。因此基於 Serverless 工做流重構 SaaS 系統靈活性方案的核心思路,是將系統內用戶最但願定製的功能進行梳理、拆分、抽離,再配合函數計算(FC)提供無狀態的能力,經過 Serverless 工做流進行這些功能點的編排,從而實現不一樣的業務流程。less
經過函數計算 FC 和 Serverless 工做流搭建靈活的訂餐模塊
訂餐場景相信你們都不會陌生,在家叫外賣或者在餐館點餐,都涉及到這個場景。當下也有不少提供點餐系統的 SaaS 服務廠商,有不少不錯的 SaaS 點餐系統。隨着消費互聯網向產業互聯網轉換,這些 SaaS 點餐系統面臨的定製化的需求也愈來愈多,其中有一個需求是不一樣的商家在支付時會顯示不一樣的支付方式,好比從 A 商家點餐後付款時顯示支付寶、微信支付、銀聯支付,從 B 商家點餐後付款時顯示支付寶、京東支付。忽然美團又冒出來了美團支付,此時 B 商家接了美團支付,那麼從 B 商家點餐後付款時顯示支付寶、京東支付、美團支付。諸如此類的定製化需求愈來愈多,這些 SaaS 產品若是沒有 PaaS 平臺,那麼就會疲於不斷的經過硬代碼增長條件判斷來實現不一樣商家的需求,這顯然不是一個可持續發展的模式。分佈式
那麼咱們來看看經過函數計算 FC 和 Serverless 工做流如何優雅的解決這個問題。先來看看這個點餐流程:
1. 經過 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 工做流。下面咱們來詳細解析這個流程。
2. 啓動流程
按常理,開始點餐時流程就應該啓動了,因此在這個示例中,個人設計是當咱們選擇完商品和商家、填完地址後啓動流程:
這裏咱們經過 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 方式的接口,傳入三個參數:
- fnfname:要啓動的流程名稱。
- execuname:流程啓動後的流程實例名稱。
- 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 方法對應。
- fnfname:要啓動的流程名稱。
- execuname:隨機生成 uuid,做爲訂單的編號,也做爲啓動流程實例的名稱。
- 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) } }) }
3. 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 ```
- name:節點名稱。
- timeoutSeconds:超時時間。該節點等待的時長,超過期間後會跳轉到 goto 分支指向的 orderCanceled 節點。
- pattern:設置爲 waitForCallback,表示須要等待確認。inputMappings:該節點入參。
- taskToken:Serverless 工做流自動生成的 Token。
- products:選擇的商品。
- supplier:選擇的商家。
- address:送餐地址。
- orderNum:訂單號。
- outputMappings:該節點的出參。
- paymentcombination:該商家支持的支付方式。
- orderNum:訂單號。
- 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 發送消息來傳遞,也是使用比較普遍的方式之一。
4. 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 函數。
整個代碼分五部分:
- 構建 Serverless 工做流 Client。
- event 內的信息即接受到 TopicgenerateInfo-fnf-demo-jiyuan 中的消息內容,將其轉換爲 Json 對象。
- 判斷什麼商家使用什麼樣的支付方式組合,這裏的示例比較簡單粗暴,正常狀況下,應該使用元數據配置的方式獲取。好比在系統內有商家信息的配置功能,經過在界面上配置該商家支持哪些支付方式,造成元數據配置信息,提供查詢接口,在這裏進行查詢。
- 調用 Java 服務暴露的接口,更新訂單信息,主要是更新支付方式。
- 給予 generateInfo 節點響應,並返回數據,這裏返回了訂單號和支付方式。由於該節點的 pattern 是 waitForCallback,因此須要等待響應結果。
5. 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.tokentarget: orderNum source: $local.orderNum - target: paymentcombination source: $local.paymentcombinationtarget: type source: $context.step.name outputMappings: - target: paymentMethod source: $local.paymentMethodtarget: orderNum source: $local.orderNum - target: price source: $local.pricetarget: taskToken source: $input.taskToken serviceParams: MessageBody: $ Priority: 1 catch:errors:FnF.TaskTimeout goto: orderCanceled
當流程流轉到 payment 節點後,意味着用戶進入了支付頁面。
這時 payment 節點會向 MNS 的 Topicpayment-fnf-demo-jiyuan 發送消息,會觸發 payment-fnf-demo 函數。
6. 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 節點。
7. 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代碼片斷以下:
8. 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 節點經過判斷支付方式流轉到具體處理支付邏輯的節點和函數。
9. 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 函數。
10. 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 折。
11. 完整流程
流程中的 orderCompleted 和 orderCanceled 節點沒作什麼邏輯,你們能夠自行發揮,思路和以前的節點同樣。因此完整的流程是這樣:
從 Serverless 工做流中看到的節點流轉是這樣的:
總結
到此,咱們基於 Serverless 工做流和 Serverless 函數計算構建的訂單模塊示例就算完成了,在示例中,有兩個點須要你們注意:
- 配置商家和支付方式的元數據規則。
- 確認支付頁面的元數據規則。
由於在實際生產中,咱們須要將可定製的部分都抽象爲元數據描述,須要有配置界面制定商家的支付方式即更新元數據規則,而後前端頁面基於元數據信息展現相應的內容。
因此若是以後須要接入其餘的支付方式,只需在 paymentCombination 路由節點中肯定好路由規則,而後增長對應的支付方式函數便可。經過增長元數據配置項,就能夠在頁面顯示新加的支付方式,而且路由處處理新支付方式的函數中。
做者 | 計緣
本文爲阿里雲原創內容,未經容許不得轉載。