Serverless 在 SaaS 領域的最佳實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"SaaS 系統面臨的挑戰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在消費互聯網時代,大家是搜索想要的東西,各個廠商在雲計算、大數據、人工智能等技術基座之上建立流量最大化的服務與生態,基於海量內容分發與流量共享爲邏輯構建系統。而到了產業互聯網時代,供給關係發生了變化,大家是定製想要的東西,需要從供給與需求兩側出發進行雙向建設,這個時候系統的靈活性和擴展性面臨着前所未有的挑戰,尤其是 ToB 的 SaaS 領域。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dd/dd2374b1563a0d5bf0c6cf57e654a826.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e2/e233c3a5f82680c614e822318d77f0be.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"如何應對挑戰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而不是所有 SaaS 公司都有財力和時間去孵化和打磨自己的 aPaaS 平臺,但市場的變化、用戶的訴求是實實在在存在的。若要生存,就要求變。這個變的核心就是能夠讓自己目前的 SaaS 系統變得靈活起來,相對建設困難的 aPaaS 平臺,我們其實可以選擇輕量且有效的 Serverless 方案來提升現有系統的靈活性和可擴展性,從而實現用戶不同的定製需求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Serverless工作流","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?__biz=MzU4NzU0MDIzOQ==&mid=2247491052&idx=3&sn=0238428fd6ef0f812722c4af0736b7ea&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"《資源成本雙優化!看Serverless顛覆編程教育的創新實踐》","attrs":{}}]},{"type":"text","text":"中,已經對Serverless的概念做過闡述了,並且也介紹了 Serverless 函數計算(FC)的概念和實踐。這篇文章中介紹一下構建系統靈活性的核心要素服務編排—— Serverless 工作流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Serverless 工作流是一個用來協調多個分佈式任務執行的全託管雲服務。在 Serverless工作流中,可以用順序、分支、並行等方式來編排分佈式任務,Serverless 工作流會按照設定好的步驟可靠地協調任務執行,跟蹤每個任務的狀態轉換,並在必要時執行您定義的重試邏輯,以確保工作流順利完成。Serverless 工作流通過提供日誌記錄和審計來監視工作流的執行,可以輕鬆地診斷和調試應用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面這張圖描述了 Serverless 工作流如何協調分佈式任務,這些任務可以是函數、已集成雲服務API、運行在虛擬機或容器上的程序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5b/5beb0182c632c051f7ea5aaf35472335.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看完 Serverless 工作流的介紹,大家可能已經多少有點思路了吧。系統靈活性和可擴展性的核心是服務可編排,無論是以前的BPM還是現在的 aPaaS。所以基於 Serverless 工作流重構SaaS系統靈活性方案的核心思路,是將系統內用戶最希望定製的功能進行梳理、拆分、抽離,再配合函數計算(FC)提供無狀態的能力,通過 Serverless 工作流進行這些功能點的編排,從而實現不同的業務流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"通過函數計算 FC 和 Serverless 工作流搭建靈活的訂餐模塊","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"訂餐場景相信大家都不會陌生,在家叫外賣或者在餐館點餐,都涉及到這個場景。當下也有很多提供點餐系統的 SaaS 服務廠商,有很多不錯的 SaaS 點餐系統。隨着消費互聯網向產業互聯網轉換,這些 SaaS 點餐系統面臨的定製化的需求也越來越多,其中有一個需求是不同的商家在支付時會顯示不同的支付方式,比如從A商家點餐後付款時顯示支付寶、微信支付、銀聯支付,從B商家點餐後付款時顯示支付寶、京東支付。突然美團又冒出來了美團支付,此時B商家接了美團支付,那麼從B商家點餐後付款時顯示支付寶、京東支付、美團支付。諸如此類的定製化需求越來越多,這些 SaaS 產品如果沒有 PaaS 平臺,那麼就會疲於不斷的通過硬代碼增加條件判斷來實現不同商家的需求,這顯然不是一個可持續發展的模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼我們來看看通過函數計算 FC 和 Serverless 工作流如何優雅的解決這個問題。先來看看這個點餐流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/90eb09e7745ed62abf075c9968e60a77.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"通過Serverless工作流創建流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首選我需要將上面用戶側的流程轉變爲程序側的流程,此時就需要使用 Serverless 工作流來擔任此任務了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打開 Serverless 控制檯,創建訂餐流程,這裏 Serverless 工作流使用流程定義語言 FDL 創建工作流,如何使用FDL創建工作流請參閱文檔。流程圖如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7a/7a1a84b9e3a34488b03370ba845e7f43.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"FDL 代碼爲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\nversion: v1beta1\ntype: flow\ntimeoutSeconds: 3600\nsteps:\n - type: task\n name: generateInfo\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: products\n source: $input.products\n - target: supplier\n source: $input.supplier\n - target: address\n source: $input.address\n - target: orderNum\n source: $input.orderNum\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentcombination\n source: $local.paymentcombination\n - target: orderNum\n source: $local.orderNum\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled\n -type: task\n name: payment\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: orderNum\n source: $local.orderNum\n - target: paymentcombination\n source: $local.paymentcombination\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentMethod\n source: $local.paymentMethod\n - target: orderNum\n source: $local.orderNum\n - target: price\n source: $local.price\n - target: taskToken\n source: $input.taskToken\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled\n - type: choice\n name: paymentCombination\n inputMappings:\n - target: orderNum\n source: $local.orderNum\n - target: paymentMethod\n source: $local.paymentMethod\n - target: price\n source: $local.price\n - target: taskToken\n source: $local.taskToken\n choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"weixin\"\n steps:\n - type: task\n name: weixin\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"unionpay\"\n steps:\n - type: task\n name: unionpay\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n default:\n goto: orderCanceled\n - type: task\n name: orderCompleted\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/orderCompleted\n end: true\n - type: task\n name: orderCanceled\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/cancerOrder","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在解析整個流程之前,我先要說明的一點是,我們不是完全通過 Serverless 函數計算和 Serverless 工作流來搭建訂餐模塊,只是用它來解決靈活性的問題,所以這個示例的主體應用是Java編寫的,然後結合了 Serverless 函數計算和 Serverless 工作流。下面我們來詳細解析這個流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"啓動流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按常理,開始點餐時流程就應該啓動了,所以在這個示例中,我的設計是當我們選擇完商品和商家、填完地址後啓動流程:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/69/696a558d19ed0a43874265e790b83d95.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們通過 Serverless 工作流提供的 OpenAPI 來啓動流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/68/6872520be386b59ffe0d83cdbca423ae.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 啓動流程","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個示例我使用 Serverless 工作流的 Java SDK,首先在 POM 文件中添加依賴:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n\n com.aliyun\n aliyun-java-sdk-core\n [4.3.2,5.0.0)\n\n\n com.aliyun\n aliyun-java-sdk-fnf\n [1.0.0,5.0.0)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後創建初始化 Java SDK 的 Config 類:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n@Configuration\npublic class FNFConfig {\n \n @Bean\n public IAcsClient createDefaultAcsClient(){\n DefaultProfile profile = DefaultProfile.getProfile(\n \"cn-xxx\", // 地域ID\n \"ak\", // RAM 賬號的AccessKey ID\n \"sk\"); // RAM 賬號Access Key Secret\n IAcsClient client = new DefaultAcsClient(profile);\n return client;\n }\n \n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再來看 Controller 中的 startFNF 方法,該方法暴露 GET 方式的接口,傳入三個參數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、fnfname:要啓動的流程名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、execuname:流程啓動後的流程實例名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、input:啓動輸入參數,比如業務參數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n @GetMapping(\"/startFNF/{fnfname}/{execuname}/{input}\")\n public StartExecutionResponse startFNF(@PathVariable(\"fnfname\") String fnfName,\n @PathVariable(\"execuname\") String execuName,\n @PathVariable(\"input\") String inputStr) throws ClientException {\n JSONObject jsonObject = new JSONObject();\n jsonObject.put(\"fnfname\", fnfName);\n jsonObject.put(\"execuname\", execuName);\n jsonObject.put(\"input\", inputStr);\n return fnfService.startFNF(jsonObject);\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再來看 Service 中的 startFNF 方法,該方法分兩部分,第一個部分是啓動流程,第二部分是創建訂單對象,並模擬入庫(示例中是放在 Map 裏了):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n @Override\n public StartExecutionResponse startFNF(JSONObject jsonObject) throws ClientException {\n StartExecutionRequest request = new StartExecutionRequest();\n String orderNum = jsonObject.getString(\"execuname\");\n request.setFlowName(jsonObject.getString(\"fnfname\"));\n request.setExecutionName(orderNum);\n request.setInput(jsonObject.getString(\"input\"));\n \n JSONObject inputObj = jsonObject.getJSONObject(\"input\");\n Order order = new Order();\n order.setOrderNum(orderNum);\n order.setAddress(inputObj.getString(\"address\"));\n order.setProducts(inputObj.getString(\"products\"));\n order.setSupplier(inputObj.getString(\"supplier\"));\n orderMap.put(orderNum, order);\n \n return iAcsClient.getAcsResponse(request);\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動流程時,流程名稱和啓動流程實例的名稱是需要傳入的參數,這裏我將每次的訂單編號作爲啓動流程的實例名稱。至於 Input,可以根據需求構造 JSON 字符串傳入。這裏我將商品、商家、地址、訂單號構造了 JSON 字符串在流程啓動時傳入流程中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,創建了此次訂單的 Order 實例,並存在 Map 中,模擬入庫,後續環節還會查詢該訂單實例更新訂單屬性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"VUE 選擇商品/商家頁面","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端我使用 VUE 搭建,當點擊選擇商品和商家頁面中的下一步後,通過 GET 方式調用 HTTP 協議的接口/startFNF/{fnfname}/{execuname}/{input}。和上面的 Java 方法對應。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、fnfname:要啓動的流程名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、execuname:隨機生成 uuid,作爲訂單的編號,也作爲啓動流程實例的名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、input:將商品、商家、訂單號、地址構建爲 JSON 字符串傳入流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" submitOrder(){\n const orderNum = uuid.v1()\n this.$axios.$get('/startFNF/OrderDemo-Jiyuan/'+orderNum+'/{\\n' +\n ' \"products\": \"'+this.products+'\",\\n' +\n ' \"supplier\": \"'+this.supplier+'\",\\n' +\n ' \"orderNum\": \"'+orderNum+'\",\\n' +\n ' \"address\": \"'+this.address+'\"\\n' +\n '}' ).then((response) => {\n console.log(response)\n if(response.message == \"success\"){\n this.$router.push('/orderdemo/' + orderNum)\n }\n })\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"generateInfo 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個節點 generateInfo,先來看看 FDL 的含義:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n - type: task\n name: generateInfo\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: products\n source: $input.products\n - target: supplier\n source: $input.supplier\n - target: address\n source: $input.address\n - target: orderNum\n source: $input.orderNum\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentcombination\n source: $local.paymentcombination\n - target: orderNum\n source: $local.orderNum\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、name:節點名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、timeoutSeconds:超時時間。該節點等待的時長,超過時間後會跳轉到 goto 分支指向的 orderCanceled 節點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、pattern:設置爲 waitForCallback,表示需要等待確認。inputMappings:該節點入參。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"taskToken:Serverless 工作流自動生成的 Token。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"products:選擇的商品。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"supplier:選擇的商家。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"address:送餐地址。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"orderNum:訂單號。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、outputMappings:該節點的出參。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"paymentcombination:該商家支持的支付方式。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"orderNum:訂單號。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5、catch:捕獲異常,跳轉到其他分支。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏 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 產生消息正文。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看完第一個節點的示例,大家可以看到,在 Serverless 工作流中,節點之間的信息傳遞可以通過集成 MNS 發送消息來傳遞,也是使用比較廣泛的方式之一。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"generateInfo-fnf-demo 函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"向 generateInfo-fnf-demo-jiyuanTopic 中發送的這條消息包含了商品信息、商家信息、地址、訂單號,表示一個下訂單流程的開始,既然有發消息,那麼必然有接受消息進行後續處理。所以打開函數計算控制檯,創建服務,在服務下創建名爲 generateInfo-fnf-demo 的事件觸發器函數,這裏選擇 Python Runtime:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/ad5044bd2c4b7ed1b9b392510d5fdfdc.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建 MNS 觸發器,選擇監聽 generateInfo-fnf-demo-jiyuanTopic。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2d/2dd6fba02f727ee53414b2835421813d.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打開消息服務 MNS 控制檯,創建 generateInfo-fnf-demo-jiyuanTopic:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/26f14cc6c8b34b8eede37052f4ed5420.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"做好函數的準備工作,我們來開始寫代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport time\nimport requests\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\n \n \ndef handler(event, context):\n # 1. 構建Serverless工作流Client\n region = \"cn-hangzhou\"\n account_id = \"XXXX\"\n ak_id = \"XXX\"\n ak_secret = \"XXX\"\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n logger = logging.getLogger()\n # 2. event內的信息即接受到Topic generateInfo-fnf-demo-jiyuan中的消息內容,將其轉換爲Json對象\n bodyJson = json.loads(event)\n logger.info(\"products:\" + bodyJson[\"products\"])\n logger.info(\"supplier:\" + bodyJson[\"supplier\"])\n logger.info(\"address:\" + bodyJson[\"address\"])\n logger.info(\"taskToken:\" + bodyJson[\"taskToken\"])\n supplier = bodyJson[\"supplier\"]\n taskToken = bodyJson[\"taskToken\"]\n orderNum = bodyJson[\"orderNum\"]\n # 3. 判斷什麼商家使用什麼樣的支付方式組合,這裏的示例比較簡單粗暴,正常情況下,應該使用元數據配置的方式獲取\n paymentcombination = \"\"\n if supplier == \"haidilao\":\n paymentcombination = \"zhifubao,weixin\"\n else:\n paymentcombination = \"zhifubao,weixin,unionpay\"\n \n # 4. 調用Java服務暴露的接口,更新訂單信息,主要是更新支付方式\n url = \"http://xx.xx.xx.xx:8080/setPaymentCombination/\" + orderNum + \"/\" + paymentcombination + \"/0\"\n x = requests.get(url)\n \n # 5. 給予generateInfo節點響應,並返回數據,這裏返回了訂單號和支付方式\n output = \"{\\\"orderNum\\\": \\\"%s\\\", \\\"paymentcombination\\\":\\\"%s\\\" \" \\\n \"}\" % (orderNum, paymentcombination)\n request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()\n request.set_Output(output)\n request.set_TaskToken(taskToken)\n resp = fnf_client.do_action_with_exception(request)\n return 'hello world'","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲 generateInfo-fnf-demo 函數配置了MNS觸發器,所以當 TopicgenerateInfo-fnf-demo-jiyuan 有消息後就會觸發執行 generateInfo-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個代碼分五部分:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、構建 Serverless 工作流 Client。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、event 內的信息即接受到 TopicgenerateInfo-fnf-demo-jiyuan 中的消息內容,將其轉換爲 Json 對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、判斷什麼商家使用什麼樣的支付方式組合,這裏的示例比較簡單粗暴,正常情況下,應該使用元數據配置的方式獲取。比如在系統內有商家信息的配置功能,通過在界面上配置該商家支持哪些支付方式,形成元數據配置信息,提供查詢接口,在這裏進行查詢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、調用Java服務暴露的接口,更新訂單信息,主要是更新支付方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5、給予 generateInfo 節點響應,並返回數據,這裏返回了訂單號和支付方式。因爲該節點的 pattern 是 waitForCallback,所以需要等待響應結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"payment節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再來看第二個節點 payment,先來看 FDL 代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n- type: task\n name: payment\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: orderNum\n source: $local.orderNum\n - target: paymentcombination\n source: $local.paymentcombination\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentMethod\n source: $local.paymentMethod\n - target: orderNum\n source: $local.orderNum\n - target: price\n source: $local.price\n - target: taskToken\n source: $input.taskToken\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當流程流轉到 payment 節點後,意味着用戶進入了支付頁面。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89efb70c22c5b645425eea0afb721a22.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時 payment 節點會向 MNS 的 Topicpayment-fnf-demo-jiyuan 發送消息,會觸發 payment-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"payment-fnf-demo函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"payment-fnf-demo 函數的創建方式和 generateInfo-fnf-demo 函數類似,這裏不再累贅。我們直接來看代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport os\nimport time\nimport logging\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\nfrom mns.account import Account # pip install aliyun-mns\nfrom mns.queue import *\n \n \ndef handler(event, context):\n logger = logging.getLogger()\n region = \"xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n mns_endpoint = \"http://your_account_id.mns.cn-hangzhou.aliyuncs.com/\"\n queue_name = \"payment-queue-fnf-demo\"\n my_account = Account(mns_endpoint, ak_id, ak_secret)\n my_queue = my_account.get_queue(queue_name)\n # my_queue.set_encoding(False)\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n eventJson = json.loads(event)\n \n isLoop = True\n while isLoop:\n try:\n recv_msg = my_queue.receive_message(30)\n isLoop = False\n # body = json.loads(recv_msg.message_body)\n logger.info(\"recv_msg.message_body:======================\" + recv_msg.message_body)\n msgJson = json.loads(recv_msg.message_body)\n my_queue.delete_message(recv_msg.receipt_handle)\n # orderCode = int(time.time())\n task_token = eventJson[\"taskToken\"]\n orderNum = eventJson[\"orderNum\"]\n output = \"{\\\"orderNum\\\": \\\"%s\\\", \\\"paymentMethod\\\": \\\"%s\\\", \\\"price\\\": \\\"%s\\\" \" \\\n \"}\" % (orderNum, msgJson[\"paymentMethod\"], msgJson[\"price\"])\n request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()\n request.set_Output(output)\n request.set_TaskToken(task_token)\n resp = fnf_client.do_action_with_exception(request)\n except Exception as e:\n logger.info(\"new loop\")\n return 'hello world'\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該函數的核心思路是等待用戶在支付頁面選擇某個支付方式確認支付。所以這裏使用了 MNS 的隊列來模擬等待。循環等待接收隊列 payment-queue-fnf-demo 中的消息,當收到消息後將訂單號和用戶選擇的具體支付方式以及金額返回給 payment 節點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"VUE選擇支付方式頁面","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲經過 generateInfo 節點後,該訂單的支付方式信息已經有了,所以對於用戶而言,當填完商品、商家、地址後,跳轉到的頁面就是該確認支付頁面,並且包含了該商家支持的支付方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b8a001fb6b53ecf2a0a2a2c6cad52172.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當進入該頁面後,會請求 Java 服務暴露的接口,獲取訂單信息,根據支付方式在頁面上顯示不同的支付方式。代碼片段如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cf/cf53cebb0734a7ae9d56ec6381eaf5ea.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當用戶選定某個支付方式點擊提交訂單按鈕後,向 payment-queue-fnf-demo 隊列發送消息,即通知 payment-fnf-demo 函數繼續後續的邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我使用了一個 HTTP 觸發器類型的函數,用於實現向 MNS 發消息的邏輯,paymentMethod-fnf-demo 函數代碼如下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n# -*- coding: utf-8 -*-\n \nimport logging\nimport urllib.parse\nimport json\nfrom mns.account import Account # pip install aliyun-mns\nfrom mns.queue import *\nHELLO_WORLD = b'Hello world!\\n'\n \ndef handler(environ, start_response):\n logger = logging.getLogger() \n context = environ['fc.context']\n request_uri = environ['fc.request_uri']\n for k, v in environ.items():\n if k.startswith('HTTP_'):\n # process custom request headers\n pass\n try: \n request_body_size = int(environ.get('CONTENT_LENGTH', 0)) \n except (ValueError): \n request_body_size = 0 \n request_body = environ['wsgi.input'].read(request_body_size) \n paymentMethod = urllib.parse.unquote(request_body.decode(\"GBK\"))\n logger.info(paymentMethod)\n paymentMethodJson = json.loads(paymentMethod)\n \n region = \"cn-xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n mns_endpoint = \"http://your_account_id.mns.cn-hangzhou.aliyuncs.com/\"\n queue_name = \"payment-queue-fnf-demo\"\n my_account = Account(mns_endpoint, ak_id, ak_secret)\n my_queue = my_account.get_queue(queue_name)\n output = \"{\\\"paymentMethod\\\": \\\"%s\\\", \\\"price\\\":\\\"%s\\\" \" \\\n \"}\" % (paymentMethodJson[\"paymentMethod\"], paymentMethodJson[\"price\"])\n msg = Message(output)\n my_queue.send_message(msg)\n \n status = '200 OK'\n response_headers = [('Content-type', 'text/plain')]\n start_response(status, response_headers)\n return [HELLO_WORLD]\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該函數的邏輯很簡單,就是向 MNS 的隊列 payment-queue-fnf-demo 發送用戶選擇的支付方式和金額。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"VUE代碼片段如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/29/294fdd298e34140faddf7bf577a1c2cf.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"paymentCombination 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"paymentCombination 節點是一個路由節點,通過判斷某個參數路由到不同的節點,這裏自然使用 paymentMethod 作爲判斷條件。FDL 代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n- type: choice\n name: paymentCombination\n inputMappings:\n - target: orderNum\n source: $local.orderNum\n - target: paymentMethod\n source: $local.paymentMethod\n - target: price\n source: $local.price\n - target: taskToken\n source: $local.taskToken\n choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"weixin\"\n steps:\n - type: task\n name: weixin\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"unionpay\"\n steps:\n - type: task\n name: unionpay\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n default:\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的流程是,用戶選擇支付方式後,通過消息發送給 payment-fnf-demo 函數,然後將支付方式返回,於是流轉到 paymentCombination 節點通過判斷支付方式流轉到具體處理支付邏輯的節點和函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"zhifubao節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們具體來看一個 zhifubao 節點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個節點的 resourceArn 和之前兩個節點的不同,這裏配置的是函數計算中函數的 ARN,也就是說當流程流轉到這個節點時會觸發 zhifubao-fnf-demo 函數,該函數是一個事件觸發函數,但不需要創建任何觸發器。流程將訂單金額、訂單號、支付方式傳給 zhifubao-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"zhifubao-fnf-demo函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們來看zhifubao-fnf-demo函數的代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport requests\nimport urllib.parse\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\n \n \ndef handler(event, context):\n region = \"cn-xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n logger = logging.getLogger()\n logger.info(event)\n bodyJson = json.loads(event)\n price = bodyJson[\"price\"]\n taskToken = bodyJson[\"taskToken\"]\n orderNum = bodyJson[\"orderNum\"]\n paymentMethod = bodyJson[\"paymentMethod\"]\n logger.info(\"price:\" + price)\n newPrice = int(price) * 0.8\n logger.info(\"newPrice:\" + str(newPrice))\n url = \"http://xx.xx.xx.xx:8080/setPaymentCombination/\" + orderNum + \"/\" + paymentMethod + \"/\" + str(newPrice)\n x = requests.get(url)\n \n return {\"Status\":\"ok\"}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"示例中的代碼邏輯很簡單,接收到金額後,將金額打8折,然後將價格更新回訂單。其他支付方式的節點和函數如法炮製,變更實現邏輯就可以。在這個示例中,微信支付打了5折,銀聯支付打7折。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"完整流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3d/3d36e7073255054593e83ff7835793d4.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從Serverless工作流中看到的節點流轉是這樣的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a3/a3918715e85a71a15dbc43484efbd6a2.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到此,我們基於 Serverless 工作流和 Serverless 函數計算構建的訂單模塊示例就算完成了,在示例中,有兩個點需要大家注意:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 配置商家和支付方式的元數據規則。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 確認支付頁面的元數據規則。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲在實際生產中,我們需要將可定製的部分都抽象爲元數據描述,需要有配置界面制定商家的支付方式即更新元數據規則,然後前端頁面基於元數據信息展示相應的內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以如果之後需要接入其他的支付方式,只需在 paymentCombination 路由節點中確定好路由規則,然後增加對應的支付方式函數即可。通過增加元數據配置項,就可以在頁面顯示新加的支付方式,並且路由到處理新支付方式的函數中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/l_aOrHvHqxzbkqKzliodng","title":""},"content":[{"type":"text","text":"Serverless 在 SaaS 領域的最佳實踐","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章