做者 | 春哥大魔王
來源 | 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,就是咱們平常基於微服務的平臺化/中臺化解決方案,均可以從中獲取設計養分在工做中應用。