首頁>科技>

隨著網際網路人口紅利逐漸減弱,基於流量的增長已經放緩,網際網路行業迫切需要找到一片足以承載自身持續增長的新藍海,產業網際網路正是這一宏大背景下的新趨勢。我們看到網際網路浪潮正在席捲傳統行業,雲計算、大資料、人工智慧開始大規模融入到金融、製造、物流、零售、文娛、教育、醫療等行業的生產環節中,這種融合稱為產業網際網路。而在產業網際網路中,有一塊不可小覷的領域是 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 選擇商品/商家頁面

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:訂單號。

4、outputMappings:該節點的出參。

paymentcombination:該商家支援的支付方式。orderNum:訂單號。

5、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 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 函式。

整個程式碼分五部分:

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-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 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"}

完整流程

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

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

總結

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

1. 配置商家和支付方式的元資料規則。

2. 確認支付頁面的元資料規則。

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

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

10
最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 小米新品被提前曝光,四曲面螢幕,左上角開孔