Deploy an example transactional workflows application in a microservices architecture on Google Cloud


This document describes how to deploy an example application using Cloud Run, Pub/Sub, Workflows, and Firestore in Datastore mode (Datastore). It's intended for application developers who want to implement transactional workflows in a microservices based application.

This document is part of a series that is composed of the following:

This example application that you deploy in this tutorial implements microservices for two architectural patterns:

  • A choreography-based saga
  • A synchronous orchestration

The application contains a web client. You can experiment with both of these patterns from the Google Cloud CLI and from the web client.

Objectives

  • Deploy server-side components for the choreography-based saga architecture
  • Deploy server-side components for the synchronous orchestration architecture
  • Test the server-side components with the curl command
  • Deploy a web application and execute workflows through it

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

When you finish the tasks that are described in this document, you can avoid continued billing by deleting the resources that you created. For more information, see Clean up.

Before you begin

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  2. Make sure that billing is enabled for your Google Cloud project.

  3. Enable the Cloud Services, Cloud Run, Workflows, Cloud Build, and Cloud Scheduler APIs.

    Enable the APIs

Clone the source code

In this section, you set your project ID and clone the source code in Cloud Shell.

  1. In Cloud Shell, set your project ID:

    PROJECT_ID=PROJECT_ID
    

    Replace PROJECT_ID with the project ID of the Google Cloud project that you created earlier.

  2. Set the project ID:

    gcloud config set project $PROJECT_ID
    
  3. Clone the tutorial repository:

    cd $HOME
    git clone https://github.com/GoogleCloudPlatform/transactional-microservice-examples
    

Deploy server-side components for choreography-based saga architectures

In this section, you deploy server-side components of the example application. The application implements choreography-based saga architectures such as microservices on Cloud Run, event publishing schedules on Cloud Scheduler, and Pub/Sub topics.

Build and deploy container images

  1. In Cloud Shell, build a container image for the Order service named order-async and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/order-async
    gcloud builds submit --tag gcr.io/$PROJECT_ID/order-service-async
    gcloud run deploy order-service-async \
      --image gcr.io/$PROJECT_ID/order-service-async \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated
    
  2. Build a container image for the Customer service named customer-async and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/customer-async
    gcloud builds submit --tag gcr.io/$PROJECT_ID/customer-service-async
    gcloud run deploy customer-service-async \
      --image gcr.io/$PROJECT_ID/customer-service-async \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated
    
  3. Build a container image for the event-publisher service and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/event-publisher
    gcloud builds submit --tag gcr.io/$PROJECT_ID/event-publisher
    gcloud run deploy event-publisher \
      --image gcr.io/$PROJECT_ID/event-publisher \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated \
      --set-env-vars "PROJECT_ID=$PROJECT_ID"
    

Create an index for Datastore

In this section, you create an index for Datastore. This index is used by the event-publisher service to select the events that must be published.

  1. In Google Cloud console, on the Datastore menu, select Datastore mode .

  2. Click Choose where to store your data and select us-east1 as the location. Then, click Create database.

  3. Create an index for Datastore:

    cd $HOME/transactional-microservice-examples/services/event-publisher
    gcloud datastore indexes create index.yaml --quiet
    
  4. In Google Cloud console, on the Datastore menu, select Indexes and wait for the status of the index to change from Indexing to Serving. This process might take a few minutes.

Create a service account to invoke microservices on Cloud Run

  • In Cloud Shell, create the cloud-run-invoker service account:

    SERVICE_ACCOUNT_NAME="cloud-run-invoker"
    SERVICE_ACCOUNT_EMAIL=${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com
    gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
      --display-name "Cloud Run Invoker"
    

    You use this service account later in this tutorial to invoke the REST APIs for the microservices that run on Cloud Run.

Define a schedule to call the event publisher service

In this section, you define a schedule to invoke the event publisher named event-publisher at one-minute intervals.

  1. In Cloud Shell, assign the run.invoker role to the event publisher service.

    SERVICE_NAME="event-publisher"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.invoker \
      --platform=managed --region=us-central1
    
  2. Define a schedule to call the Event publisher service named event-publisher at one minute intervals by using the cloud-run-invoker service account:

    SERVICE_NAME="event-publisher"
    SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)"
      --filter="SERVICE:${SERVICE_NAME}")
    SERVICE_URL="${SERVICE_URL}/api/v1/event/publish"
    gcloud scheduler jobs create http event-publisher-scheduler \
      --schedule='* * * * *' \
      --http-method=GET \
      --uri=$SERVICE_URL \
      --oidc-service-account-email=$SERVICE_ACCOUNT_EMAIL \
      --oidc-token-audience=$SERVICE_URL \
      --location=us-central1
    

Create Pub/Sub topics

  • In Cloud Shell, create the following Pub/Sub topics:

    gcloud pubsub topics create order-service-event
    gcloud pubsub topics create customer-service-event
    

The order-service-event topic is used by the Order service and the event-publisher service to publish events from the Order service. The customer-service-event topic is used to publish events from the Customer service.

Define a topic to send event notifications to microservices

In this section, you define the push-subscription topic to deliver messages in Pub/Sub topics to microservices.

  1. In Cloud Shell, assign the iam.serviceAccountTokenCreator role to the project Pub/Sub service account:

    PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format "value(projectNumber)")
    gcloud projects add-iam-policy-binding $PROJECT_ID \
      --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com \
      --role=roles/iam.serviceAccountTokenCreator
    

    This command lets the service account create an access token to invoke microservices on Cloud Run.

  2. Assign the run.invoker role to the customer-service-async service:

    SERVICE_NAME="customer-service-async"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.invoker \
      --platform=managed --region=us-central1
    
  3. Create a push-subscription topic:

    SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    SERVICE_URL="${SERVICE_URL}/api/v1/customer/pubsub"
    gcloud pubsub subscriptions create push-order-to-customer \
      --topic order-service-event \
      --push-endpoint=$SERVICE_URL \
      --push-auth-service-account=$SERVICE_ACCOUNT_EMAIL
    

    This topic delivers messages in the order-service-event topic to the Customer service using the cloud-run-invoker service account.

  4. Assign the run.invoker role to the order-service-async service:

    SERVICE_NAME="order-service-async"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.invoker \
      --platform=managed --region=us-central1
    
  5. Create a push-subscription topic:

    SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    SERVICE_URL="${SERVICE_URL}/api/v1/order/pubsub"
    gcloud pubsub subscriptions create push-customer-to-order \
      --topic customer-service-event \
      --push-endpoint=$SERVICE_URL \
      --push-auth-service-account=$SERVICE_ACCOUNT_EMAIL
    

    The push-subscription topic delivers messages in the customer-service-event topic to the Order service by using the cloud-run-invoker service account.

Deploy server-side components for a synchronous orchestration architecture

In this section, you deploy the server-side components for the example application. These components implement a synchronous orchestration architecture on Cloud Run, as well as a workflow which is executed with Workflows.

Build and deploy container images

In this section, you build container images for microservices and deploy them on Cloud Run.

  1. In Cloud Shell, build a container image for the Order service named order-sync and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/order-sync
    gcloud builds submit --tag gcr.io/$PROJECT_ID/order-service-sync
    gcloud run deploy order-service-sync \
      --image gcr.io/$PROJECT_ID/order-service-sync \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated
    
  2. Build a container image for the Customer service named customer-sync and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/customer-sync
    gcloud builds submit --tag gcr.io/$PROJECT_ID/customer-service-sync
    gcloud run deploy customer-service-sync \
      --image gcr.io/$PROJECT_ID/customer-service-sync \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated
    
  3. Build a container image for the Order processor service named order-processor and deploy it on Cloud Run:

    cd $HOME/transactional-microservice-examples/services/order-processor
    gcloud builds submit --tag gcr.io/$PROJECT_ID/order-processor-service
    gcloud run deploy order-processor-service \
      --image gcr.io/$PROJECT_ID/order-processor-service \
      --platform=managed --region=us-central1 \
      --no-allow-unauthenticated \
      --set-env-vars "PROJECT_ID=$PROJECT_ID"
    

Create a service account to invoke microservices on Cloud Run

In this section, you reuse the cloud-run-invoker service account that you created in Create a service account to invoke microservices on Cloud Run.

Deploy a workflow to process an order

  1. In Cloud Shell, assign the run.invoker and run.viewer roles to the cloud-run-invoker service account for the order-service-sync service.

    SERVICE_NAME="order-service-sync"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.invoker \
      --platform=managed --region=us-central1
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.viewer \
      --platform=managed --region=us-central1
    
  2. Assign run.invoker and run.viewer roles to the cloud-run-invoker service account for the customer-service-sync service:

    SERVICE_NAME="customer-service-sync"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.invoker \
      --platform=managed --region=us-central1
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
      --role=roles/run.viewer \
      --platform=managed --region=us-central1
    
  3. Use the cloud-run-invoker service account to deploy a workflow:

    SERVICE_NAME="order-service-sync"
    ORDER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    SERVICE_NAME="customer-service-sync"
    CUSTOMER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    cd $HOME/transactional-microservice-examples/services/order-processor
    cp order_workflow.yaml.template order_workflow.yaml
    sed -i "s#ORDER-SERVICE-URL#${ORDER_SERVICE_URL}#" order_workflow.yaml
    sed -i "s#CUSTOMER-SERVICE-URL#${CUSTOMER_SERVICE_URL}#" order_workflow.yaml
    
    gcloud beta workflows deploy order_workflow \
      --source=order_workflow.yaml \
      --service-account=$SERVICE_ACCOUNT_EMAIL
    

Test choreography-based saga architecture components

In this section, you test components deployed on the choreography-based saga architecture using the curl command.

  • In Cloud Shell, set environment variables that point to URLs of API endpoints for the customer-service-async and order-service-async microservices:

    SERVICE_NAME="customer-service-async"
    CUSTOMER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    SERVICE_NAME="order-service-async"
    ORDER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    

Create a customer entry

  • In Cloud Shell, create a customer ID named customer01 by sending an API request to the Customer service named customer-service-async:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d '{"customer_id":"customer01", "limit":10000}' \
      -s ${CUSTOMER_SERVICE_URL}/api/v1/customer/limit | jq .
    

    The output is similar to the following:

    {
      "credit": 0,
      "customer_id": "customer01",
      "limit": 10000
    }
    

Submit an order

In this section, you submit an order and trigger an order ID assignment for it.

  1. In Cloud Shell, submit an order by sending an API request to the Order service named order-service-async:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d '{"customer_id":"customer01", "number":10}' \
      -s ${ORDER_SERVICE_URL}/api/v1/order/create | jq .
    

    In this case, you order 10 items by specifying the following: "number": 10

    The output is similar to the following:

    {
      "customer_id": "customer01",
      "number": 10,
      "order_id": "720d1305-b6fd-4f57-aaf4-fd2ca5bdfe1e",
      "status": "pending"
    }
    

    The "order_id" flag in the output shows the unique order ID which is assigned to the order. Copy this ID because you use it in the next step.

  2. Set the order ID in an environment variable:

    ORDER_ID=ORDER_ID
    

    Replace ORDER_ID with the "order_id" flag that you copied in the previous step.

Check an order status

In this section, you check how an order status changes.

  1. In Cloud Shell, get the status of the order by sending an API request to the Order service named order-service-async:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d "{\"customer_id\":\"customer01\", \"order_id\":\"$ORDER_ID\"}" \
      -s ${ORDER_SERVICE_URL}/api/v1/order/get | jq .
    

    The output resembles the following:

    {
      "customer_id": "customer01",
      "number": 10,
      "order_id": "720d1305-b6fd-4f57-aaf4-fd2ca5bdfe1e",
      "status": "pending"
    }
    

    The "status" flag in the output shows the status of the order. If the transactional process is still running, the status is "pending". In this case, wait a few minutes and check the status again by running the same command. When the transactional process is complete, the status shows as "accepted".

  2. Get customer information for the customer01 customer ID:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d '{"customer_id":"customer01"}' \
      -s ${CUSTOMER_SERVICE_URL}/api/v1/customer/get | jq .
    

    The output resembles the following:

    {
      "credit": 1000,
      "customer_id": "customer01",
      "limit": 10000
    }
    

    The "credit" flag shows the current credit usage of the customer. It increases by 1,000 because the business logic for this transaction is to increase the credit by 100 for one item.

    You can repeat the order process by repeating the steps in Submit an order. If you order 100 items by specifying "number": 100, the final status of the order is "rejected" because the credit usage goes over the limit.

Test synchronous orchestration architecture components

In this section, you test the deployed components of the synchronous orchestration architecture using the curl command.

  • In Cloud Shell, set the environment variables that point to URLs for the API endpoints of the customer-service-sync, order-service-sync, and order-processor-service microservices:

    SERVICE_NAME="customer-service-sync"
    CUSTOMER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    SERVICE_NAME="order-service-sync"
    ORDER_SERVICE_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    SERVICE_NAME="order-processor-service"
    ORDER_PROCESSOR_URL=$(gcloud run services list --platform managed \
      --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    

Create a customer entry

  • In Cloud Shell send an API request to the Customer service named customer-service-sync to create a customer ID named customer02:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d '{"customer_id":"customer02", "limit":10000}' \
      -s ${CUSTOMER_SERVICE_URL}/api/v1/customer/limit | jq .
    

    The output resembles the following:

    {
      "credit": 0,
      "customer_id": "customer02",
      "limit": 10000
    }
    

Submit an order

In this section, you submit an order and check the result.

  1. In Cloud Shell, submit an order by sending an API request to Order service order-processor-service:

    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      -H "Content-Type: application/json" \
      -d '{"customer_id":"customer02", "number":10}' \
      -s ${ORDER_PROCESSOR_URL}/api/v1/order/process | jq .
    

    In this case, you order 10 items by