Serverless 在 SaaS 領域的最佳實踐

2021-01-15 阿里云云棲號

簡介:隨著網際網路人口紅利逐漸減弱,基於流量的增長已經放緩,網際網路行業迫切需要找到一片足以承載自身持續增長的新藍海,產業網際網路正是這一宏大背景下的新趨勢。我們看到網際網路浪潮正在席捲傳統行業,雲計算、大數據、人工智慧開始大規模融入到金融、製造、物流、零售、文娛、教育、醫療等行業的生產環節中,這種融合稱為產業網際網路。而在產業網際網路中,有一塊不可小覷的領域是 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 工作流如何優雅的解決這個問題。先來看看這個點餐流程:

1. 通過 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 工作流。下面我們來詳細解析這個流程。

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 類:

@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 方式的接口,傳入三個參數:

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:![8.png](https://ucc.alicdn.com/pic/developer-ecology/291e93740e48446197f29aa179a5f7cb.png)創建 MNS 觸發器,選擇監聽 generateInfo-fnf-demo-jiyuanTopic。![9.png](https://ucc.alicdn.com/pic/developer-ecology/880c91561a6747d59c29bb7894eb727a.png)打開消息服務 MNS 控制臺,創建 generateInfo-fnf-demo-jiyuanTopic:![10.png](https://ucc.alicdn.com/pic/developer-ecology/e9ab6266f1f046e08a78b9144c797db0.png)做好函數的準備工作,我們來開始寫代碼:-- 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 函數。整個代碼分五部分:- 構建 Serverless 工作流 Client。- event 內的信息即接受到 TopicgenerateInfo-fnf-demo-jiyuan 中的消息內容,將其轉換為 Json 對象。- 判斷什麼商家使用什麼樣的支付方式組合,這裡的示例比較簡單粗暴,正常情況下,應該使用元數據配置的方式獲取。比如在系統內有商家信息的配置功能,通過在界面上配置該商家支持哪些支付方式,形成元數據配置信息,提供查詢接口,在這裡進行查詢。- 調用 Java 服務暴露的接口,更新訂單信息,主要是更新支付方式。- 給予 generateInfo 節點響應,並返回數據,這裡返回了訂單號和支付方式。因為該節點的 pattern 是 waitForCallback,所以需要等待響應結果。## 5. payment 節點我們再來看第二個節點 payment,先來看 FDL 代碼:type: taskname: paymenttimeoutSeconds: 300resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messagespattern: waitForCallbackinputMappings:target: taskTokensource: $context.task.tokentarget: orderNumsource: $local.orderNum - target: paymentcombination source: $local.paymentcombinationtarget: typesource: $context.step.name outputMappings: - target: paymentMethod source: $local.paymentMethodtarget: orderNumsource: $local.orderNum - target: price source: $local.pricetarget: taskTokensource: $input.taskToken serviceParams: MessageBody: $Priority: 1catch:errors: FnF.TaskTimeoutgoto: 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 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-mnsfrom 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 loggingimport urllib.parseimport jsonfrom mns.account import Account # pip install aliyun-mnsfrom 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 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 折。

11. 完整流程

流程中的 orderCompleted 和 orderCanceled 節點沒做什麼邏輯,大家可以自行發揮,思路和之前的節點一樣。所以完整的流程是這樣:

從 Serverless 工作流中看到的節點流轉是這樣的:

總結

到此,我們基於 Serverless 工作流和 Serverless 函數計算構建的訂單模塊示例就算完成了,在示例中,有兩個點需要大家注意:

配置商家和支付方式的元數據規則。確認支付頁面的元數據規則。因為在實際生產中,我們需要將可定製的部分都抽象為元數據描述,需要有配置界面制定商家的支付方式即更新元數據規則,然後前端頁面基於元數據信息展示相應的內容。

所以如果之後需要接入其他的支付方式,只需在 paymentCombination 路由節點中確定好路由規則,然後增加對應的支付方式函數即可。通過增加元數據配置項,就可以在頁面顯示新加的支付方式,並且路由到處理新支付方式的函數中。

作者:計緣

本文為阿里雲原創內容,未經允許不得轉載

相關焦點

  • 使用Jenkins、Docker 構建部署 Serverless 應用
    serverless 命令行是使用 nodejs 開發的,所以會需要 nodejs 環境。 此外,計劃部署的應用是用 python 開發的,也會需要到 python 環境。
  • Serverless Framework 2.18.0 發布 - OSCHINA - 中文開源技術交流...
    AWSiotFleetProvisioning事件支持 Standalone:針對獨立二進位文件更新到 Node 14Bug 修復 Config Schema:改進 AWS tags validationMaintenance Improvements更新說明:https://github.com/serverless
  • AWS發布新一代Amazon Aurora Serverless
    要了解更多信息,請訪問https://aws.amazon.com/aurora/serverless。隨著微軟越來越積極且操縱性地使用其許可實踐,客戶要求AWS幫助他們更方便地將其SQL Server應用程式遷移到Amazon Aurora。 Babelfish for Aurora PostgreSQL是一項新的功能,讓客戶幾乎無需更改代碼即可直接在PostgreSQL上運行SQL Server應用程式成為可能。
  • 自然語言處理的最佳實踐
    此存儲庫包含構建 NLP 系統的示例和最佳實踐,在 jupyter notebook 和實用程序函數中提供。知識庫的重點是最先進的方法和常見的場景,這些方法和場景在研究文本和語言問題的研究人員和實踐者中很流行。概述該知識庫的目標是利用 NLP 算法、神經架構和分布式機器學習系統的最新進展構建一套綜合的工具和示例。
  • 實錄|河南自貿試驗區2020年最佳實踐案例新聞發布會
    9月10日,河南省人民政府新聞辦公室舉行「河南自貿試驗區2020年最佳實踐案例」新聞發布會,重點發布河南自貿試驗區2020年最佳實踐案例總體情況,以及河南自貿試驗區建設和全面深化改革創新情況,並回答記者提問。省委宣傳部新聞發布辦公室主任常軼暘主持新聞發布會。
  • RESTful JSON Web服務最佳實踐
    最近Edwin發布了一本關於構建基於JSON的Web服務最佳實踐的cookbook。當然這還在進行當中,但現有提供的指南包括了:  第一階段-定義一個簡單的資源/服務 | 選一個示例資源比如客戶信息,用JSON來對其建模。構建一個簡單的servlet,以PUT來創建一個新客戶,以GET基於客戶鍵值返回客戶信息,以DELETE刪除客戶,以POST更新客戶信息。
  • DDD最佳實踐-理解領域驅動設計中的聚合
    引言如果從 Eric Evans 出版《領域驅動設計》[1] 算起,領域驅動設計 (Domain Driven Design , DDD)的概念已經產生 14 年了。十幾年時間很長,但是 DDD 的概念結構卻很穩定,沒有出現本質的變更。
  • 這裡有份最佳實踐清單
    對於如何使用深度學習進行自然語言處理,本文作者 Sebastian Ruder 給出了一份詳細的最佳實踐清單,不僅包括與大多數 NLP 任務相關的最佳實踐,還有最常見任務的最佳實踐,尤其是分類、序列標註、自然語言生成和神經機器翻譯。作者對最佳實踐的選擇很嚴格,只有被證明在至少兩個獨立的群體中有益的實踐才會入選,並且每個最佳實踐作者至少給出兩個參引。
  • 河南自貿試驗區23個最佳實踐案例發布,這項「河南經驗」被國務院...
    河南商報首席記者 楊桂芳9月10日,河南商報記者從省政府新聞辦新聞發布會上獲悉,河南自貿試驗區2020年最佳實踐案例發布。都有哪些案例成為制度創新性強、市場主體反映好、具有一定系統集成特點的經驗做法?這些創新案例又給企業帶來哪些政策紅利?
  • 論文發表的最佳實踐:道德規範和iThenticate軟體
    作為英國皇家科學院(UK Academy of Science)下屬機構,我們始終著眼於傳播優秀的科學論文——因此,我們致力於不斷確保我們的發表業務在各個方面都符合最佳實踐標準。 Royal Society Open Science是我們最熱門的期刊之一,該刊涉及各個科學領域,並實行開放獲取。
  • 智能無人機領域課程思政教育實踐
    結合國防科大技術型軍校的特點,筆者在本科生智能無人機領域的教學實踐中,嘗試將思政教育引入課程教學。主要包括將「家國情懷」引入課堂,樹立「科學無國界,但科學家有國界」的觀點;採用「以學生為中心,科研服務於教學」的教學理念,引導科研成果進課堂等。在培養智能無人時代的研發和保障人才方面做了有益的實踐探索。
  • 人工智慧在網絡領域的應用與實踐有哪些
    打開APP 人工智慧在網絡領域的應用與實踐有哪些 發表於 2019-07-03 16:31:14 作為一個誕生了60多年的詞彙,人工智慧的發展已經歷過多輪起伏,現今這個階段可以說是人工智慧的黃金時期,全球的科技巨頭都在AI領域頻頻布局,不同於單純的炒作,此輪「AI熱」最大的特色就是落地。
  • 中山大學管理學院講座預告丨案例研究近路之爭:案例研究最佳實踐...
    中山大學管理學與經濟學系列前沿講座之四一四講暨「案例思享會」專題講座第17講講座主題案例研究近路之爭:案例研究最佳實踐主講嘉賓李平 教授(寧波諾丁漢大學)主持人梁劍平 副教授講座時間2020
  • 福州航空榮獲福建民航工匠精神文化建設「最佳實踐獎」
    海航集團旗下福州航空榮獲福建轄區「最佳實踐獎」;福州航空維修質控班組長詹正明榮獲福建省「金牌工人」榮譽稱號,所在班組獲得福建省總工會頒發的「工人先鋒號」榮譽稱號。福州航空維修工程部質控班組獲得「工人先鋒號」福州航空獲得「最佳實踐獎
  • Serverless 架構下 Python 輕鬆搞定圖像分類
    前言圖像分類是人工智慧領域的一個熱門話題。通俗解釋就是,根據各自在圖像信息中所反映的不同特徵,把不同類別的目標區分開來的圖像處理方法。它利用計算機對圖像進行定量分析,把圖像或圖像中的每個像元或區域劃歸為若干個類別中的某一種,以代替人的視覺判讀。圖像分類在實際生產生活中也是經常遇到的,而且針對不同領域或者需求有著很強的針對性。
  • 知識圖譜在金融資管領域的應用、實踐與展望
    金融知識圖譜作為專業領域知識圖譜,在智能投研、智能風控、智能客服、智能合規等領域有著重要的應用價值。本文綜合熵簡科技三年以來的產業實踐,結合知識圖譜領域的技術前沿,以及資管場景的落地應用,淺談知識圖譜在金融資管領域的發展現狀與應用展望。1、金融資管知識圖譜的獨特之處根據知識圖譜項目的應用場景,可以分為通用知識圖譜、專業領域知識圖譜。
  • 客戶成功(Customer Success):定義、因素和最佳實踐
    在這篇文章中,本文作者對客戶成功的定義、因素和最佳實踐進行了闡述。眾所周知,要成功,每個企業都需要出色的銷售和營銷團隊。但是,在當今這樣的當今時代,客戶擁有比以往更多的選擇,僅靠銷售和營銷不足以維持增長。 企業不再依賴於年度合同來鎖定客戶。此外,最近十年來,獲取客戶的成本急劇增加。
  • [視頻]三:上海世博會首創「城市最佳實踐區」 提前感知未來城市生活
    作為本屆世博會的亮點之一,上海世博園將首次設立「城市最佳實踐區」,一個集合全球先進生活方式的社區,帶您提前感受未來的城市生活。    「滬上生態家」是上海唯一入選的案例,也是最早完成結構封頂。它的原型是上海市郊區的一座生態示範樓。作為中國首座「零能耗」生態示範樓,該建築的一大優點是高效利用太陽能,屋頂上巨大的太陽能光熱設備以及風能裝置可為整幢樓提供能源。
  • Kotlin最佳實踐:在高階函數中使用inline - 碼農登陸
    最佳實踐當我們沒有高階函數、沒有使用reified關鍵詞時不應該隨意使用inline,徒增消耗。尾聲到此這篇文章就結束了。但是看了外國小哥這篇文章的時候,的確發現自己有很多內容是有遺漏的。所以接下來如果有機會的話,會繼續寫或者翻譯一些這類「最佳實踐」的文章。
  • 【最佳法治實踐案例】依法監管、合力共治,再造網絡新生態
    【最佳法治實踐案例】依法監管、合力共治,再造網絡新生態 2019-08-22 17:46 來源:澎湃新聞·澎湃號·政務