Serverless best practices in the SaaS field

With the gradual decline of the Internet demographic dividend and traffic-based growth has slowed, the Internet industry urgently needs to find a new blue ocean that can support its own continuous growth. The industrial Internet is a new trend in this grand context. We see that the Internet wave is sweeping through traditional industries. Cloud computing, big data, and artificial intelligence are beginning to be integrated into the production links of finance, manufacturing, logistics, retail, entertainment, education, and medical industries on a large scale. This integration is called the industrial Internet. . In the industrial Internet, one area that cannot be underestimated is the SaaS field, which is the middle force of the ToB track, such as CRM, HRM, cost control systems, financial systems, collaborative office, and so on.

Challenges faced by SaaS systems

In the consumer Internet era, everyone is searching for what they want. Various manufacturers build services and ecosystems that maximize traffic on the basis of technology such as cloud computing, big data, and artificial intelligence, and build systems based on the logic of mass content distribution and traffic sharing. . In the age of the industrial Internet, the supply relationship has changed. Everyone is customizing what they want. Two-way construction needs to be carried out from both sides of supply and demand. At this time, the flexibility and scalability of the system are facing unprecedented challenges, especially ToB The SaaS field.

Especially for the current economic environment, SaaS vendors must understand that they can no longer burn money and only focus on the number of their own users, and they have to think more about how to help customers reduce costs and increase efficiency, so more Focus on the customization capabilities of their products.

How to deal with the challenge

Salesforce, the leader in the SaaS field, extends the concept of CRM to Marketing, Sales, and Service. Among these three areas, only Sales has specialized SaaS products. The other two areas are industry solutions for various ISVs in different industries. What does it depend on? Undoubtedly, it is Salesforce's powerful aPaaS platform. ISVs, internal implementations, and customers can all use the aPaaS platform to build SaaS systems in their own industries and fields in their respective dimensions to establish a complete ecosystem. So in my opinion, the current Salesforce has been upgraded from a SaaS company to an aPaaS platform company. This evolutionary process also confirms the transition logic of the consumer Internet and the industrial Internet and the core demands of the latter.

However, not all SaaS companies have the financial resources and time to incubate and polish their own aPaaS platforms, but market changes and user demands are real. To survive, it requires change. The core of this change is to be able to make our current SaaS system more flexible. Compared with the difficult aPaaS platform to build, we can actually choose a lightweight and effective Serverless solution to improve the flexibility and scalability of the existing system. Different customization needs of users.

Serverless workflow

In the previous article "Double optimization of resource cost! "See Serverless Subversion of the Innovative Practice of Programming Education", the concept of Serverless has been explained, and the concept and practice of Serverless Functional Computing (FC) have also been introduced. This article introduces the core element service orchestration of building system flexibility-Serverless workflow.

Serverless workflow is a fully managed cloud service used to coordinate the execution of multiple distributed tasks. In a serverless workflow, distributed tasks can be arranged in sequence, branching, parallel, etc. The serverless workflow will reliably coordinate task execution according to the set steps, track the state transition of each task, and execute it when necessary You define the retry logic to ensure the smooth completion of the workflow. Serverless workflow monitors the execution of the workflow by providing logging and auditing, which can easily diagnose and debug applications.

The following figure describes how serverless workflows coordinate distributed tasks. These tasks can be functions, integrated cloud service APIs, and programs running on virtual machines or containers.

After reading the introduction of Serverless workflow, you may have some ideas. The core of system flexibility and scalability is service orchestration, whether it is the previous BPM or the current aPaaS. Therefore, the core idea of ​​reconstructing the flexibility of the SaaS system based on Serverless workflow is to sort out, split, and extract the functions that users most want to customize in the system, and then cooperate with function computing (FC) to provide stateless capabilities. Workflow arranges these function points to realize different business processes.

Build a flexible meal ordering module through function calculation FC and Serverless workflow

I believe everyone is familiar with the ordering scene. Ordering food at home or ordering food at a restaurant involves this scene. There are also many SaaS service vendors that provide ordering systems, and there are many good SaaS ordering systems. With the transition from the consumer Internet to the industrial Internet, these SaaS ordering systems are facing more and more customized needs. One of them is that different merchants will display different payment methods when paying, such as ordering from merchant A. Alipay, WeChat Pay, and UnionPay are displayed for post-payment, and Alipay and JD Pay are displayed for payment after ordering from Merchant B. Suddenly, Meituan Pay came out again from Meituan. Merchant B accepted Meituan Pay, and then Alipay, JD Pay, and Meituan Pay were displayed when ordering food from Merchant B and paying. There are more and more customized needs such as this. If these SaaS products do not have a PaaS platform, they will be tired of constantly increasing the condition of hard code to meet the needs of different businesses. This is obviously not a sustainable development model.

So let's take a look at how FC and Serverless workflows can be used to solve this problem elegantly. Let's take a look at this ordering process first:

1. Create a process through a serverless workflow

First of all, I need to transform the above user-side process into a program-side process. At this time, I need to use a serverless workflow to take on this task.

Open the Serverless console and create the ordering process. Here, the Serverless workflow uses the process definition language FDL to create the workflow. Please refer to the documentation for how to use FDL to create the workflow. The flow chart is shown in the figure below:

The FDL code is:

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

Before analyzing the entire process, I want to explain that we do not completely build the ordering module through Serverless function calculation and Serverless workflow, but only use it to solve the problem of flexibility, so the main application of this example is written in Java , And then combine Serverless function calculation and Serverless workflow. Let's analyze this process in detail.

2. Startup process

According to common sense, the process should start when you start ordering, so in this example, my design is to start the process after we have selected the product and the merchant, and filled in the address:

Here we start the process through the OpenAPI provided by the Serverless workflow.

  • Java startup process

For this example, I use the Java SDK of the Serverless workflow. First, add the dependency in the POM file:

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

Then create the Config class that initializes the Java SDK:

@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;
    }

}

Let's look at the startFNF method in the Controller. This method exposes the GET interface and passes in three parameters:

  • fnfname: The name of the process to be started.
  • execuname: The name of the process instance after the process is started.
  • input: Start input parameters, such as business parameters.
@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);
    }

Let's look at the startFNF method in Service. The method is divided into two parts. The first part is the startup process, and the second part is to create an order object and simulate storage (in the example, it is placed in the 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);
    }

When starting the process, the process name and the name of the start process instance are the parameters that need to be passed in. Here I use the order number each time as the instance name of the start process. As for Input, you can construct a JSON string according to your needs. Here I construct a JSON string of products, merchants, addresses, and order numbers and pass them into the process when the process starts.

In addition, an Order instance of this order is created and stored in the Map to simulate warehousing. The subsequent links will query the order instance to update the order attributes.

  • VUE select product/merchant page

I use VUE to build the front end. When I click the next step in the select product and merchant page, I call the HTTP protocol interface /startFNF/{fnfname}/{execuname}/{input} through GET. Corresponds to the above Java method.

  • fnfname: The name of the process to be started.
  • execuname: Randomly generate uuid as the order number and the name of the startup process instance.
  • input: Construct the product, merchant, order number, and address as a JSON string and pass it into the process.
submitOrder(){
                const orderNum = uuid.v1()
                this.$axios.$get('/startFNF/OrderDemo-Jiyuan/'+orderNum+'/{\n' +
                    '  "products": "'+this.products+'",\n' +
                    '  "supplier": "'+this.supplier+'",\n' +
                    '  "orderNum": "'+orderNum+'",\n' +
                    '  "address": "'+this.address+'"\n' +
                    '}' ).then((response) => {
                    console.log(response)
                    if(response.message == "success"){
                        this.$router.push('/orderdemo/' + orderNum)
                    }
                })
            }

3. generateInfo node

The first node generateInfo, let's take a look at the meaning of 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: Node name.
  • timeoutSeconds: timeout period. The waiting time of this node will jump to the orderCanceled node pointed to by the goto branch after the time expires.
  • pattern: set to waitForCallback, indicating that you need to wait for confirmation. inputMappings: The node inputs parameters.
  • taskToken: Token automatically generated by Serverless workflow.
  • products: selected products.
  • supplier: The selected merchant.
  • address: delivery address.
  • orderNum: order number.
  • outputMappings: The output parameters of this node.
  • paymentcombination: The payment method supported by the merchant.
  • orderNum: order number.
  • catch: catch the exception and jump to other branches.

Here resourceArn and serviceParams need to be explained separately. Serverless workflow supports integration with multiple cloud services, that is, other services are used as execution units of task steps. The service integration method is expressed by FDL language. In the task step, resourceArn can be used to define the integrated target service, and pattern can be used to define the integration mode. So you can see that the acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages information is configured in the resourceArn, that is, the MNS message queue service is integrated in the generateInfo node, and when the generateInfo node is triggered, it will send to generateInfo-fnf Send a message in -demo-jiyuanTopic. Then the message body and parameters are specified in the serviceParams object. MessageBody is the message body, and the configuration $ means that the message body is generated by input mapping inputMappings.

After reading the example of the first node, you can see that in the serverless workflow, the information transfer between nodes can be passed by integrating MNS to send messages, which is also one of the more widely used methods.

4.

The message sent by the generateInfo-fnf-demo function to generateInfo-fnf-demo-jiyuanTopic contains product information, business information, address, and order number, indicating the beginning of an order process. Since there is a message, it must be Receive the message for subsequent processing. So open the function computing console, create a service, and create an event trigger function named generateInfo-fnf-demo under the service. Here, select Python Runtime:

create an MNS trigger, and choose to monitor generateInfo-fnf-demo-jiyuanTopic.

Open the message service MNS console and create generateInfo-fnf-demo-jiyuanTopic:

Prepare the function, let’s start writing the code:

-- 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. Build Serverless Workflow 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. The information in the event is received

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. Determine which merchant uses what payment method combination

The example here is relatively simple and rude. Under normal circumstances, you should use the metadata configuration method to obtain:

paymentcombination = ""
if supplier == "haidilao":
    paymentcombination = "zhifubao,weixin"
else:
    paymentcombination = "zhifubao,weixin,unionpay"

4. Call the interface exposed by the Java service to update the order information, mainly to update the payment method

url = "http://xx.xx.xx.xx:8080/setPaymentCombination/" + orderNum + "/" + paymentcombination + "/0"
x = requests.get(url)

5. Give a response to the generateInfo node and return the data, where the order number and payment method are returned

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'

Because the generateInfo-fnf-demo function is configured with an MNS trigger, when TopicgenerateInfo-fnf-demo-jiyuan has a message, it will trigger the execution of the generateInfo-fnf-demo function.

The entire code is divided into five parts:

  • Build a serverless workflow client.
  • The information in the event receives the message content in TopicgenerateInfo-fnf-demo-jiyuan and converts it into a Json object.
  • To determine what combination of payment methods are used by merchants, the example here is relatively simple and rude. Under normal circumstances, it should be obtained by means of metadata configuration. For example, there is a configuration function for merchant information in the system. By configuring which payment methods the merchant supports on the interface, the metadata configuration information is formed, and the query interface is provided, and the query is performed here.
  • Call the interface exposed by the Java service to update the order information, mainly to update the payment method.
  • Give a response to the generateInfo node and return the data, where the order number and payment method are returned. Because the pattern of this node is waitForCallback, it needs to wait for the response result.

5. Payment node

Let's look at the second node payment, first look at the FDL code:

type: task
name: payment
timeoutSeconds: 300
resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages
pattern: waitForCallback
inputMappings:target: taskToken
source: $context.task.tokentarget: orderNum
source: $local.orderNum - target: paymentcombination source: $local.paymentcombinationtarget: type
source: $context.step.name outputMappings: - target: paymentMethod source: $local.paymentMethodtarget: orderNum
source: $local.orderNum - target: price source: $local.pricetarget: taskToken
source: $input.taskToken serviceParams: MessageBody: $
Priority: 1
catch:errors:FnF.TaskTimeout
goto: orderCanceled

When the process flow goes to the payment node, it means that the user has entered the payment page.

At this time, the payment node will send a message to Topicpayment-fnf-demo-jiyuan of MNS, which will trigger the payment-fnf-demo function.

6. payment-fnf-demo function

The creation method of the payment-fnf-demo function is similar to the generateInfo-fnf-demo function, so it will not be redundant here. Let's look at the code directly:

# -*- 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'

The core idea of ​​this function is to wait for the user to select a payment method to confirm payment on the payment page. Therefore, the MNS queue is used to simulate waiting. Circulately wait for the message in the payment-queue-fnf-demo to receive the message. When the message is received, the order number and the specific payment method and amount selected by the user are returned to the payment node.

7. VUE Choose Payment Method Page

Because after the generateInfo node, the payment method information for the order is available, for the user, after filling in the product, merchant, and address, the page that jumps to is the confirmation payment page, and contains the merchant’s support payment method.

After entering the page, it will request the interface exposed by the Java service to obtain the order information, and display different payment methods on the page according to the payment method. The code snippet is as follows:

When the user selects a payment method and clicks the submit order button, a message is sent to the payment-queue-fnf-demo queue, that is, the payment-fnf-demo function is notified to continue the subsequent logic.

Here I use an HTTP trigger type function to implement the logic of sending messages to MNS. The paymentMethod-fnf-demo function code is as follows.

# -*- 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]

The logic of this function is very simple, that is, send the payment method and amount selected by the user to the MNS queue payment-queue-fnf-demo.
The VUE code snippet is as follows:

 

8. paymentCombination node

The paymentCombination node is a routing node, which is routed to different nodes by judging a certain parameter. Here, paymentMethod is naturally used as the judgment condition. The FDL code is as follows:

- 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

The process here is that after the user selects the payment method, it sends a message to the payment-fnf-demo function, and then returns the payment method, and then flows to the paymentCombination node to determine the payment method to flow to the nodes and functions that process the payment logic.

9. zhifubao node

Let's look at a zhifubao node specifically:

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

The resourceArn of this node is different from the previous two nodes. The ARN of the function in function calculation is configured here, which means that when the process flow goes to this node, the zhifubao-fnf-demo function will be triggered, which is an event-triggered function. But there is no need to create any triggers. The process passes the order amount, order number, and payment method to the zhifubao-fnf-demo function.

10. zhifubao-fnf-demo function

Now we look at the code of the zhifubao-fnf-demo function:

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

The code logic in the example is very simple. After receiving the amount, the amount is discounted by 20%, and then the price is updated back to the order. The nodes and functions of other payment methods are the same, and the implementation logic can be changed. In this example, WeChat Pay has a 50% discount and UnionPay has a 30% discount.

11. Complete process

The orderCompleted and orderCanceled nodes in the process do not have any logic, you can use it yourself, and the idea is the same as the previous nodes. So the complete process is like this:

The node flow seen from the Serverless workflow is like this:

to sum up

At this point, we have completed the order module example built based on Serverless workflow and Serverless function calculations. In the example, there are two points that need attention:

  • Configure metadata rules for merchants and payment methods.
  • Confirm the metadata rules of the payment page.

Because in actual production, we need to abstract the customizable parts as metadata descriptions, and need to have a configuration interface to formulate the merchant's payment method, that is, to update the metadata rules, and then the front-end page displays the corresponding content based on the metadata information.

So if you need to access other payment methods later, you only need to determine the routing rules in the paymentCombination routing node, and then add the corresponding payment method functions. By adding metadata configuration items, the newly added payment method can be displayed on the page and routed to the function for processing the new payment method.

 

Author | Ji Yuan

 

Original link

This article is the original content of Alibaba Cloud and may not be reproduced without permission.

Guess you like

Origin blog.csdn.net/weixin_43970890/article/details/112569927