Conditional Transaction Demo

This demo simulates a partial backend of an e-commerce application including four services:

  • payment
  • membership
  • inventory
  • supplier

Note Please go through the dependency-free-transaction-demo first, before proceeding with this demo.

Background

Many e-commerce sites have membership concept, which will level up when a customer makes a certain amount of purchase. Moreover, as goods are sold and the stock drops to a certain level, the e-commerce company needs to fetch more goods from its suppliers. Both scenarios introduce conditional transactions.

Workflow

                  /---> if total purchase >= 1K, level up membership
start ---> make payment ---> dispatch product from inventory ---> end
                                              \---> if product stock < 10, replenish product from supplier 

The conditions are business logic and belong to business service. Therefore, the decision to level up membership is made in payment service and the decision to replenish products is made in inventory service. Saga is to be informed of service decision by adding an additional element sagaChildren in service response JSON to indicate which services are to be invoked next as shown below.

{
  "customerId": "mike",
  "body": "Payment made with id xxx for customer mike",
  "sagaChildren": ["inventory"] 
}

If sagaChildren is empty or not provided in service response, saga will invoke all of its child services. To invoke none of them, explicitly return "sagaChildren": ["none"].

If any of the sub-transaction specified in sagaChildren fails, all completed sub-transactions will be compensated as usual, when the recovery policy if backward recovery.

Running Demo

  1. run the following command to create docker images in saga project root folder.
mvn package -DskipTests -Pdocker -Pdemo
  1. start application up in saga/saga-demo/conditional-transaction-demo with the following command
docker-compose up

User Requests

The request JSON to ensure the workflow order looks like the following:

{
  "policy": "BackwardRecovery",
  "requests": [
    {
      "id": "payment",
      "type": "rest",
      "serviceName": "payment.servicecomb.io:8080",
      "transaction": {
        "method": "post",
        "path": "/payment",
        "params": {
          "form": {
            "customerId": "mike",
            "purchaseAmount": 400
          }
        }
      },
      "compensation": {
        "method": "put",
        "path": "/payment",
        "params": {
          "form": {
            "customerId": "mike"
          }
        }
      }
    },
    {
      "id": "membership",
      "type": "rest",
      "serviceName": "membership.servicecomb.io:8080",
      "parents": [
        "payment"
      ],
      "transaction": {
        "method": "post",
        "path": "/membership",
        "params": {
          "form": {
            "customerId": "mike"
          }
        }
      },
      "compensation": {
        "method": "put",
        "path": "/membership",
        "params": {
          "form": {
            "customerId": "mike"
          }
        }
      }
    },
    {
      "id": "inventory",
      "type": "rest",
      "serviceName": "inventory.servicecomb.io:8080",
      "parents": [
        "payment"
      ],
      "transaction": {
        "method": "post",
        "path": "/inventory",
        "params": {
          "form": {
            "customerId": "mike"
          }
        }
      },
      "compensation": {
        "method": "put",
        "path": "/inventory",
        "params": {
          "form": {
            "customerId": "mike"
          }
        }
      }
    },
    {
      "id": "supplier",
      "type": "rest",
      "serviceName": "supplier.servicecomb.io:8080",
      "parents": [
        "inventory"
      ],
      "transaction": {
        "method": "post",
        "path": "/supplier",
        "params": {
          "form": {
            "customerId": "servicecomb_mall"
          }
        }
      },
      "compensation": {
        "method": "put",
        "path": "/supplier",
        "params": {
          "form": {
            "customerId": "servicecomb_mall"
          }
        }
      }
    }
  ]
}

To send the above JSON request to Saga, use postman with POST request to url http://<docker.host.ip>:8083/requests

Each request to payment service will increase user mike's total purchase by $400. The 3rd request will trigger membership level up.

The initial product stock is 11 in inventory and each request to inventory service will deduct product stock by 1. So the 2nd request will trigger product replenishment from supplier.

Note transactions and compensations implemented by services must be idempotent. In this demo, we did not enforce that for simplicity.

To see all events generated by Saga, visit http://<docker.host.ip>:8083/events with postman.