Cloud Native 28 min read

How to Build a Flexible SaaS Ordering System with Serverless Workflows and Functions

This article explains how SaaS providers can overcome rigidity by using Alibaba Cloud Serverless Workflow and Function Compute to orchestrate a customizable order‑processing module, detailing the architecture, FDL definitions, Java SDK integration, Vue front‑end, and payment routing logic.

Alibaba Cloud Native
Alibaba Cloud Native
Alibaba Cloud Native
How to Build a Flexible SaaS Ordering System with Serverless Workflows and Functions

As the internet’s population dividend wanes, growth based on traffic is slowing, prompting the industry to seek new blue‑sea opportunities. The rise of the industrial internet—where cloud computing, big data, and AI are embedded into traditional sectors—has highlighted SaaS as a crucial B2B component, encompassing CRM, HRM, expense control, finance, and collaboration tools.

In the industrial‑internet era, SaaS faces unprecedented challenges: systems must be highly flexible and extensible to meet diverse, custom‑made requirements from enterprise customers, rather than relying on mass traffic. Companies can no longer grow solely by burning cash to acquire users; they must enable deep product customization.

While building a full aPaaS platform is costly, a lightweight and effective alternative is to adopt Serverless solutions. Serverless Workflow, a fully managed cloud service, can coordinate distributed tasks (functions, APIs, VM programs) using sequential, branching, or parallel flows, providing reliable execution, retry logic, logging, and audit capabilities.

The core idea is to reconstruct SaaS flexibility by extracting the most frequently customized features, exposing them as independent functions, and orchestrating those functions with Serverless Workflow. The example below demonstrates a complete order‑processing module built with Java, Vue, Serverless Function Compute (FC), and Serverless Workflow.

1. Defining the workflow with FDL

The workflow is described using the Flow Definition Language (FDL). The following snippet shows the full definition, including task nodes, a choice node, and end nodes:

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

The workflow coordinates the order lifecycle: generateInfo → payment → paymentCombination (routing to a specific payment method) → orderCompleted or orderCanceled.

2. generateInfo node

This task sends a message to an MNS topic (generateInfo‑fnf‑demo‑jiyuan) and waits for a callback. Input mappings pass the task token, product list, supplier, address, and order number. Output mappings return the chosen payment combination and order number.

3. generateInfo‑fnf‑demo function (Python)

# -*- coding: utf-8 -*-
import logging, json, requests
from aliyunsdkcore.client import AcsClient
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest, ReportTaskFailedRequest

def handler(event, context):
    region = "cn-hangzhou"
    account_id = "XXXX"
    ak_id = "XXX"
    ak_secret = "XXX"
    fnf_client = AcsClient(ak_id, ak_secret, region)
    logger = logging.getLogger()
    body = json.loads(event)
    supplier = body["supplier"]
    taskToken = body["taskToken"]
    orderNum = body["orderNum"]
    # Simple rule: different suppliers support different payment combos
    if supplier == "haidilao":
        paymentcombination = "zhifubao,weixin"
    else:
        paymentcombination = "zhifubao,weixin,unionpay"
    # Update order via a Java‑exposed API (demo URL)
    url = f"http://xx.xx.xx.xx:8080/setPaymentCombination/{orderNum}/{paymentcombination}/0"
    requests.get(url)
    output = f"{{\"orderNum\": \"{orderNum}\", \"paymentcombination\": \"{paymentcombination}\"}}"
    req = ReportTaskSucceededRequest.ReportTaskSucceededRequest()
    req.set_Output(output)
    req.set_TaskToken(taskToken)
    fnf_client.do_action_with_exception(req)
    return 'hello world'

4. Starting the workflow from Java

Add the following Maven dependencies:

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

Configure the SDK client:

@Configuration
public class FNFConfig {
    @Bean
    public IAcsClient createDefaultAcsClient(){
        DefaultProfile profile = DefaultProfile.getProfile(
                "cn-xxx", // region
                "ak",    // AccessKey ID
                "sk"); // AccessKey Secret
        return new DefaultAcsClient(profile);
    }
}

Expose a GET endpoint to start the flow:

@GetMapping("/startFNF/{fnfname}/{execuname}/{input}")
public StartExecutionResponse startFNF(@PathVariable("fnfname") String fnfName,
                                      @PathVariable("execuname") String execuName,
                                      @PathVariable("input") String inputStr) throws ClientException {
    JSONObject json = new JSONObject();
    json.put("fnfname", fnfName);
    json.put("execuname", execuName);
    json.put("input", inputStr);
    return fnfService.startFNF(json);
}

The service creates a StartExecutionRequest, sets the flow name, execution name (order number), and input JSON, stores a temporary Order object in a map, and calls getAcsResponse to launch the workflow.

5. Vue front‑end for order creation

When the user selects a product, supplier, and address, the front‑end calls the Java start‑flow API:

submitOrder(){
    const orderNum = uuid.v1();
    this.$axios.get(`/startFNF/OrderDemo-Jiyuan/${orderNum}/{
        "products": "${this.products}",
        "supplier": "${this.supplier}",
        "orderNum": "${orderNum}",
        "address": "${this.address}"
    }`).then(response => {
        if(response.message == "success"){
            this.$router.push(`/orderdemo/${orderNum}`);
        }
    })
}

6. payment node

After the user reaches the payment page, the workflow sends a message to the MNS topic payment‑fnf‑demo‑jiyuan, triggering the payment‑fnf‑demo function.

7. payment‑fnf‑demo function (Python)

# -*- coding: utf-8 -*-
import logging, json, time, requests
from aliyunsdkcore.client import AcsClient
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest
from mns.account import Account

def handler(event, context):
    logger = logging.getLogger()
    region = "cn-xxx"
    ak_id = "xxx"
    ak_secret = "xxx"
    mns_endpoint = "http://your_account_id.mns.cn-hangzhou.aliyuncs.com/"
    queue_name = "payment-queue-fnf-demo"
    account = Account(mns_endpoint, ak_id, ak_secret)
    queue = account.get_queue(queue_name)
    fnf_client = AcsClient(ak_id, ak_secret, region)
    eventJson = json.loads(event)
    while True:
        try:
            msg = queue.receive_message(30)
            queue.delete_message(msg.receipt_handle)
            msgJson = json.loads(msg.message_body)
            task_token = eventJson["taskToken"]
            orderNum = eventJson["orderNum"]
            output = f"{{\"orderNum\": \"{orderNum}\", \"paymentMethod\": \"{msgJson[\"paymentMethod\"]}\", \"price\": \"{msgJson[\"price\"]}\"}}"
            req = ReportTaskSucceededRequest.ReportTaskSucceededRequest()
            req.set_Output(output)
            req.set_TaskToken(task_token)
            fnf_client.do_action_with_exception(req)
            break
        except Exception as e:
            logger.info("retry waiting for payment message")
    return 'hello world'

This function polls the MNS queue until a payment‑method message arrives, then reports success back to the workflow.

8. paymentCombination choice node

Based on the paymentMethod returned by the previous step, the workflow routes to the corresponding payment‑specific task (zhifubao, weixin, or unionpay). The default branch leads to orderCanceled.

9. Payment‑method functions (zhifubao, weixin, unionpay)

Each function receives price, orderNum, and paymentMethod, applies a discount, updates the order via the Java API, and returns ok. Example for zhifubao:

# -*- coding: utf-8 -*-
import logging, json, requests
from aliyunsdkcore.client import AcsClient
from aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest

def handler(event, context):
    body = json.loads(event)
    price = int(body["price"])
    new_price = int(price * 0.8)  # 20% discount for zhifubao
    orderNum = body["orderNum"]
    paymentMethod = body["paymentMethod"]
    url = f"http://xx.xx.xx.xx:8080/setPaymentCombination/{orderNum}/{paymentMethod}/{new_price}"
    requests.get(url)
    return {"Status": "ok"}

Weixin applies a 50% discount, unionpay a 30% discount; the logic can be customized as needed.

10. Complete flow

The final workflow includes placeholder orderCompleted and orderCanceled nodes where developers can add business‑specific actions. The overall execution path is:

11. Takeaways

Define merchant‑payment metadata so that the workflow can retrieve supported payment methods dynamically.

Expose the payment‑selection page as a Vue component that queries the order API and displays the appropriate options.

To add a new payment channel, simply extend the paymentCombination choice node with a new branch and implement the corresponding function.

This example illustrates how Serverless Workflow and Function Compute can be combined to give SaaS products the flexibility and extensibility required in the industrial‑internet era.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaCloud NativeServerlessworkflowVueFunction ComputeFDL
Alibaba Cloud Native
Written by

Alibaba Cloud Native

We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.