Chapter 2: Building with Fauna + AWS
Step-Fuctions

AWS Step Function

AWS Step Function is a serverless orchestration service that lets developers create and manage multi-step application workflows. Use cases for Step Functions vary widely, from orchestrating serverless microservices, to building data-processing pipelines.

In this application we will use Step Function to orchestrate the Lambda functions in our vacation booking application.

Step Functions configuration

To get started, first install the serverless-step-functions plugin. Run the following command.

$ npm i serverless-step-functions --save

Add, the step function dependency in your serverless.yml file.

 
# partials of serverless.yml
 
service: fauna-aws-workshop
 
frameworkVersion: '2.72.3'
 
# Specify donenv
useDotenv: true
 
# Add Fauna plugin
plugins:
  - serverless-dotenv-plugin
  - "@fauna-labs/serverless-fauna"
  - serverless-step-functions
 
#...rest of the file

Next, you will create a state machine to orchestrate the Lambdas. Add the following code snippet to serverless.yml file.

# partials of serverless.yml
 
stepFunctions:
  stateMachines:
    bookVacationStepFunc:
      events:
        - http:
            method: POST
            path: /book-vacation
            cors: true
      definition:
        Comment: "Booking Hotel Step"
        StartAt: BookHotel
        States:
          BookHotel:
            Type: Task
            Resource:
              Fn::GetAtt: [bookHotel, Arn]
            Next: BookFlight
          BookFlight:
            Type: Task
            Resource: 
              Fn::GetAtt: [bookFlight, Arn]
            Catch:
            - ErrorEquals: ["States.TaskFailed"]
              Next: CancelHotel
            End: true
          CancelHotel:
            Type: Task
            Resource: 
              Fn::GetAtt: [cancelHotel, Arn]
            End: true

View the entire serverles.yml file here.

Run sls deploy command to deploy the latest changes.

When the deployment is done head back to AWS console. Select Step Function > State machines > your-step-function-name > Edit.

Untitled

Notice that a Step Function workflow has been generated. Your Step Function workflow should be as follows.

Untitled

The deployment also generates an API endpoint. Make a post-call to your generated API endpoint to check everything is working.

Successful booking scenario

POST [https://pc4jk9bsil.execute-api.us-east-1.amazonaws.com/dev/book-vacation](https://www.notion.so/AWS-Step-Function-d5adcb5d2b23487bbe704f612c31d612)

// payload
{
    "userId": "TEST-TEST-3",
    "flightId": 123
}

Observe the Step Function execution in your AWS console. You will be able to notice something similar as follows.

Untitled

Failed booking scenario

POST [https://pc4jk9bsil.execute-api.us-east-1.amazonaws.com/dev/book-vacation](https://www.notion.so/AWS-Step-Function-d5adcb5d2b23487bbe704f612c31d612)

// payload
{
    "userId": "TEST-fail-2",
    "flightId": -1
}

Untitled

At this point you have your event driven architecture setup.

Listening to DB changes with Fauna Event Streams

To make this solution work end to end let’s go ahead and create a simple frontend. In the fronted you can also subscribe to any changes that happens in the database. Fauna’s Event Stream API gives you the ability to subscribe to database events in real time.

✍️ Learn more about Event Streaming here.

Create a new file called index.html in the root of your application. Add the following code.

<html>
  <h1>Example Frontend</h1>
  <body>
    <input type="text" id="userId">
    <p>
      <span class="scores"></span>
    </p>
    <button onclick="createNewVacation()">Book Vacation</button>
  </body>
 
<script src="https://cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb-min.js"></script>
<script type="text/javascript">
 
  const faunadb = window.faunadb
  const q = faunadb.query
 
  const client = new faunadb.Client({
    secret: 'fnAxxxx',
    domain: 'db.fauna.com', // Adjust if you are using Region Groups
  })
 
  function report(e) {
    console.log(e)
  }
 
  async function createNewVacation() {
    const userId = document.getElementById('userId').value;
    const myHeaders = new Headers();
    myHeaders.append("Content-Type", "application/json");
 
    const raw = JSON.stringify({
      userId,
      "flightId": "231233"
    });
 
    let requestOptions = {
      method: 'POST',
      headers: myHeaders,
      body: raw,
      redirect: 'follow'
    };
 
    let stream;
 
    // https://ecos7fl9ec.execute-api.us-east-1.amazonaws.com/dev/book-vacation
 
    setTimeout(() => {
      stream.close();
    }, 100000);
 
    try {
      const response = await fetch("https://ic6e58n5gk.execute-api.us-east-1.amazonaws.com/dev/book-vacation", requestOptions);
      
      setTimeout(() => {
        console.log("starting stream");
        
        client.query(
          q.Get(
            q.Match(q.Index('vacations_by_userId'), userId)
          )
        )
        .then((ret) => console.log(ret))
        .catch((err) => console.error(
          'Error: [%s] %s: %s',
          err.name,
          err.message,
          err.errors()[0].description,
        ))
        const startStream = () => {
          
          if(stream) {
            stream.close()
          }
 
          stream = client.stream.document(docRef)
          .on('snapshot', snapshot => {
            report(snapshot)
          })
          .on('version', version => {
            report(version)
          })
          .on('error', error => {
            console.log('Error:', error)
            stream.close()
            setTimeout(startStream, 1000)
          })
          .start()
        }
      }, 5000);
 
    } catch (error) {
      console.log(error);
    }
  }
  
 
  // startStream()
  
</script>
 
</html>

⚠️ Caution: While doing Event Streaming make sure to close the subscription when not in use. If you keep the subscription open (i.e. Open browser tab with subscribed stream) you will be charged for the usage.

In this next section you learn how to add event sourcing to this architecture.

Last updated on August 5, 2022