tree: 1041eebe63b87b9d90a4bff10c90356d1daf4b22 [path history] [tgz]
  1. car-rental-service/
  2. demo-tests/
  3. flight-booking-service/
  4. hotel-reservation-service/
  5. payment-service/
  6. docker-compose.yaml
  7. pom.xml
  8. README.md
saga-demo/README.md

Saga Demo

This demo simulates a bare minimal travel application including four services:

  • car rental
  • flight booking
  • hotel reservation
  • payment

Background

A user with travel plans may like making car rental, flight booking, and hotel reservation with just a single service instead of three separate services. That's why this application can help.

With microservice architecture, each of the services may have its own database technology and it's not feasible to ensure all transactions on these services are either committed or rolled back with database. In this demo, we make use of Saga to ensure eventual data consistency among services.

Architecture

               
               /----> car rental service
User ---> Saga -----> flight booking service
               \----> hotel reservation service
                \---> payment service

Running Demo

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

User Requests

A user normally expects to make payment only when transactions with all three services are completed successfully. So Saga will talk to car rental, flight booking, and hotel reservation services in parallel and then payment service only when the former three services return success.

The request JSON to ensure this process order looks like the following:

[
  {
    "id": "request-car",
    "type": "rest",
    "serviceName": "car.servicecomb.io:8080",
    "transaction": {
      "method": "post",
      "path": "/rentals",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    },
    "compensation": {
      "method": "put",
      "path": "/rentals",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    }
  },
  {
    "id": "request-hotel",
    "type": "rest",
    "serviceName": "hotel.servicecomb.io:8080",
    "transaction": {
      "method": "post",
      "path": "/reservations",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    },
    "compensation": {
      "method": "put",
      "path": "/reservations",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    }
  },
  {
    "id": "request-flight",
    "type": "rest",
    "serviceName": "flight.servicecomb.io:8080",
    "transaction": {
      "method": "post",
      "path": "/bookings",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    },
    "compensation": {
      "method": "put",
      "path": "/bookings",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    }
  },
  {
    "id": "request-payment",
    "type": "rest",
    "serviceName": "payment.servicecomb.io:8080",
    "parents": [
      "request-car",
      "request-flight",
      "request-hotel"
    ],
    "transaction": {
      "method": "post",
      "path": "/payments",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    },
    "compensation": {
      "method": "put",
      "path": "/payments",
      "params": {
        "json": {
          "body": "{ \"customerId\": \"mike\" }"
        }
      }
    }
  }
]

The key to the request dependency lies in definition of parents field in the payment request. It means the payment request is only executed after request-car , request-flight , and request-hotel are completed.

    "id": "request-payment",
    "type": "rest",
    "serviceName": "payment.servicecomb.io:8080",
    "parents": [
      "request-car",    // request id to car service in the full request json
      "request-flight", // request id to flight service in the full request json
      "request-hotel"   // request id to hotel service in the full request json
    ]

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

Sending the request more than once will trigger compensation due to insufficient account balance in payment-service.

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