logo

Refactoring a serverless application to use Step Functions third-party API call integration

With the new third-party API invoke feature in Step Functions, we might not need a Lambda function to invoke external HTTPS endpoints. When we remove it, the state machine will become smaller and easier to maintain, and our logic might run faster.

Published Dec 15, 2023

I recently created a weather application for a demo that collects and displays temperature data at my location.
I use the OpenWeather API to receive the current temperature data by providing the latitude and longitude coordinates as query strings in the request. A Lambda function invokes the weather API and sends the current temperature value as a custom metric to CloudWatch. I then graph the temperature values on a dashboard and activate an alarm when the temperature sinks below zero degrees Celsius.
I use EventBridge Scheduler to run a cron job and trigger the Lambda function every X minutes.
I store the API key as a secure string in Systems Manager Parameter Store. The function calls the GetParameter API to receive the secret at runtime and stores it in memory. This way, we can reduce the number of calls to Parameter Store and reuse the execution environment as long as possible.
Existing X-Ray service map
Existing X-Ray service map
I also installed Powertools for AWS Lambda as a dependency. It allows me to easily add the custom Temperature metric as an embedded metric format, so that CloudWatch can extract the metric data from the log events.
I also send some annotations and subsegments to X-Ray that makes it easy to identify bottlenecks and Lambda cold starts.

I had been happy with my new mini-application when AWS announced support for direct HTTPS endpoint invocations in Step Functions at re:Invent 2023. With this new feature, we might not need any Lambdas that call the 3rd party API in the state machine! I wanted to see if I could refactor the application and remove the function from the workflow while keeping all the other features.

Let us see how the direct third-party HTTP endpoint integration works at a high level.
Step Functions uses the same API as EventBridge API destinations to call HTTP endpoints. We should follow the same procedure as if we set up an HTTP target for a rule in EventBridge.
So we create a connection first and specify authorization pattern, which is API key in this case. EventBridge Connections uses Secrets Manager to store the key.
The new state
The new state
The next step is to head over to the Step Functions page, select the Call third-party API task from the left menu in Workflow Studio, and drop it onto the canvas. We configure the endpoint, method, applicable query strings, and body payload. Incorporating the new state works like adding other Tasks to the state machine.

So I started redesigning the application with Step Functions with the third-party API invoke feature.

A good practice when designing serverless applications is to use Lambda to transform and not transport and integrate services directly to each other whenever possible. The Call third-party API state does that with external APIs.
I also wanted to keep sending the Temperature custom metric to CloudWatch, so I added a CloudWatch:PutMetricData Task to the state machine.
The state machine has become very simple, and it looks like this:
The new state machine
The new state machine

Next, we investigate the Amazon State Language (ASL) code for the Task states.
Here is the structure of the Call third-party API state:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"OpenWeather API": {
"Type": "Task",
"Resource": "arn:aws:states:::http:invoke",
"Parameters": {
"ApiEndpoint": "https://api.openweathermap.org/data/2.5/weather",
"Method": "GET",
"Authentication": {
"ConnectionArn": "arn:aws:events:eu-central-1:ACCOUNT_ID:connection/OpenWeatherMapApi/CONNECTION_ID"
},
"QueryParameters": {
"lat": "LATITUDE COORDINATE",
"lon": "LONGITUDE COORDINATE",
"units": "metric"
}
},
"Next": "Pass",
"ResultSelector": {
"temperature.$": "$.ResponseBody.main.temp"
}
}
We can see that the Parameters object contains all the data that Step Functions needs to invoke the OpenWeather API. We should specify the API endpoint, the Method, and the query strings. The Authentication object references the EventBridge Connection that adds the API key to the call. Step Functions wraps the response in the ResponseBody object.
The OpenWeather API returns a large object with different weather data for the given location. However, I decided to keep only the temperature because I only need that for my CloudWatch dashboard. We can use ResultSelector in Step Functions to extract properties from the Task result. In this case, we create a new object where the key is temperature and the value is the current temperature returned from the API.
The output of the state will look like this:
1
2
3
{
"temperature": 2.4
}
We can apply this optimization technique because we need only the temperature value in the next state.

Because I have removed the Lambda function from the logic, I needed to add another state that pushes the temperature to CloudWatch. Step Functions directly SDK-integrates with more than 10,000 AWS APIs. Luckily, PutMetricData is one of them, so we can use it to send the temperature data to the dashboard.
The Task state looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"PutMetricData": {
"Type": "Task",
"Parameters": {
"MetricData": [
{
"MetricName": "Temperature",
"Unit": "Count",
"Value.$": "$.temperature",
"Dimensions": [
{
"Name": "CurrentWeather",
"Value": "temperature"
}
]
}
],
"Namespace": "StepFnWeatherApp"
},
"Resource": "arn:aws:states:::aws-sdk:cloudwatch:putMetricData",
"Next": "Success"
}
The most important part of this definition is the MetricData array, where we can define the specifications of the given metric. We must append the $ to the Value key because we extract its value dynamically from the input.

The best part of refactoring the application is that I don't have to write a single line of application code!
Step Function can handle retries and errors, so we could eliminate the if/else and try/catch blocks earlier from our code. With the new third-party API invoke state, we can delegate another task to Step Functions!

The other Step Functions announcement in re:Invent 2023 was the TestState API. We can test each state in isolation without executing the entire state machine using the new API.
TestState API brings us closer to the step-by-step console.log-style development. We can add the state to the final workflow if we are confident it works. It's a great feature and provides a different way to develop applications with Step Functions and create state machines in the Workflow Studio.

I created the application with both Standard and Express workflows.
In my case, the Standard workflow ran in about 1 second, but the Express workflow was inconsistent due to the execution history overhead. It sometimes needed around 0.3-0.4 seconds to run but every 3rd-4th execution took about 4 seconds.
Comparing them to version 1 (~2 seconds with a Lambda cold start and ~1 second with a warm start), I experienced slightly faster execution times with the Standard workflow. I can save the cold start time, which, although not critical for me, can be an advantage in some use cases.
Another important factor when choosing the architecture is cost. According to the AWS Calculator, I would pay $0.33 for the Standard workflow (including the 4000 free state transitions per month) and $0 for the Express (not enough invocations to make it billable) and for Version 1 with Lambda (the free tier covers it).
In my case (2880 invocations per month, six state changes per invocation), there is not much difference in the cost. Every use case is different, so you should evaluate the situation and create a solution that best fits your business needs.

Step Functions direct 3rd party HTTP integration adds a new tool to our serverless developer's toolbox. From now we don't have to use Lambda functions to invoke external endpoints, which might result in faster execution times and simpler architecture.
We can also test our states separately without executing the entire state machine. This way, we can ensure that the task we add to the state machine returns the expected result before incorporating it into the workflow.

Call third-party APIs - Setting up direct 3rd party calls in Step Functions
Workflow Studio - Everything about the Workflow Studio
Publish custom metrics - Adding custom metrics to CloudWatch
Standard vs. Express Workflows - Comparison between the workflow types. When to choose which?