隨著網際網路人口紅利逐漸減弱,基於流量的增長已經放緩,網際網路行業迫切需要找到一片足以承載自身持續增長的新藍海,產業網際網路正是這一宏大背景下的新趨勢。我們看到網際網路浪潮正在席捲傳統行業,雲計算、大資料、人工智慧開始大規模融入到金融、製造、物流、零售、文娛、教育、醫療等行業的生產環節中,這種融合稱為產業網際網路。而在產業網際網路中,有一塊不可小覷的領域是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方式的介面,傳入三個引數:
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中,模擬入庫,後續環節還會查詢該訂單例項更新訂單屬性。
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) } }) }
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傳送訊息來傳遞,也是使用比較廣泛的方式之一。
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函式。
整個程式碼分五部分:
構建Serverless工作流Client。event內的資訊即接受到TopicgenerateInfo-fnf-demo-jiyuan中的訊息內容,將其轉換為Json物件。判斷什麼商家使用什麼樣的支付方式組合,這裡的示例比較簡單粗暴,正常情況下,應該使用元資料配置的方式獲取。比如在系統內有商家資訊的配置功能,透過在介面上配置該商家支援哪些支付方式,形成元資料配置資訊,提供查詢介面,在這裡進行查詢。呼叫Java服務暴露的介面,更新訂單資訊,主要是更新支付方式。給予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-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節點。
VUE選擇支付方式頁面因為經過generateInfo節點後,該訂單的支付方式資訊已經有了,所以對於使用者而言,當填完商品、商家、地址後,跳轉到的頁面就是該確認支付頁面,並且包含了該商家支援的支付方式。
當進入該頁面後,會請求Java服務暴露的介面,獲取訂單資訊,根據支付方式在頁面上顯示不同的支付方式。程式碼片段如下:
這裡我使用了一個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程式碼片段如下:
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函式計算構建的訂單模組示例就算完成了,在示例中,有兩個點需要大家注意:
配置商家和支付方式的元資料規則。確認支付頁面的元資料規則。因為在實際生產中,我們需要將可定製的部分都抽象為元資料描述,需要有配置介面制定商家的支付方式即更新元資料規則,然後前端頁面基於元資料資訊展示相應的內容。
所以如果之後需要接入其他的支付方式,只需在paymentCombination路由節點中確定好路由規則,然後增加對應的支付方式函式即可。透過增加元資料配置項,就可以在頁面顯示新加的支付方式,並且路由到處理新支付方式的函式中。