Rafal WilinskiBlogConsulting

March 14, 2021 / 5 minutes / #AWS #DynamoDB #NoSQL #GraphQL #Serverless

Upgrading Javascript AWS-SDK to v3

In December 2020, AWS released AWS SDK v3 for Javascript. It promises to be modular and has a more tree-shakeable structure, resulting in smaller artifact sizes. This is especially important if you're interacting with AWS resources inside Lambda functions where each KB of code affects your cold-start time and 💸. Unfortunately, migration isn't straightforward. The signature of new V3 calls is new - some functions are gone, and some are replaced with new modules.

In this blog post, I show upgrading strategies from AWS SDK V2 to V3 for a few most common use cases in Serverless, at least for me. Official docs are describing three migration paths, but for the purpose of this article, we'll focus only on the 3rd Path - the proper one using the Command objects and fully async/await based approach.

DynamoDB

The most frequent complaint regarding the new DynamoDB client from AWS SDK V3 was a lack of DocumentClient library. Instead, we were forced to call marshall() / unmarshall() whenever inserting, putting or getting items from DynamoDB. This was introducing a lot of unnecessary complexity and caused code to be harder to follow and test.

Fortunately, AWS DynamoDB Document Client has been recently added to the V3.

import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'
import { DocumentClient } from 'aws-sdk/clients/dynamodb'
const ddb = new DocumentClient()
const TableName = 'my-table'
const Item = {
id: '1',
name: 'Dynobase',
}
await ddb
.put({
TableName,
Item,
})
.promise()
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'

In V2, once we had our DocumentClient constructed, interaction with DynamoDB in Node.js was boiling down to using methods like get, put, scan or query and ending them with .promise() call.

In V3, DocumentClient is imported from a different package and created using DynamoDBDocumentClient.from() method. Moreover, you can call the document client operations using Command objects.

If you don't like this new style, you can still use the old .put() method too. However, in this case, you need to import DynamoDBDocument instead of DynamoDBDocumentClient.


S3

Interacting with Simple Storage from Lambdas is a widespread pattern. Let's talk about uploading, downloading, and preparing pre-signed URLs.

Uploading files

import { S3 } from 'aws-sdk'
const s3 = new S3()
const bucket = 'my-bucket'
const key = 'cat.png'
const fileContent = fs.readFileSync('path-to-file.jpg')
await s3
.upload({
Bucket: bucket,
Key: key,
Body: fileContent,
})
.promise()
const fileContent = fs.readFileSync('path-to-file.jpg')

The complexity of uploading large files to S3 in V2 is hidden inside the upload method. It takes care of initiating the multipart upload, uploading parts, and calculating checksums. There's also a famous .promise() call at the end to promisify the call.

In V3, we are not only forced to import SDK in a modular way, but also we have to import two packages: client-s3 - responsible for communicating with AWS S3, and lib-storage, which is a high-level helper library containing classes like Upload which now take care of multipart upload orchestration.

Generating presigned URLs

Pre-signed URLs are useful when you need to expose some data to the client in a secure way or allow them to upload files from the client directly to the S3. In such a scenario, the client requests a backend for a pre-signed URL, the backend sends it back to the client, and the client initiates uploading procedure to the S3 directly using provided URL.

import { S3 } from 'aws-sdk'
const s3 = new S3()
const params = { Bucket: 'bucket', Key: 'key' }
const url = s3.getSignedUrl('getObject', params) // or getSignedUrlPromise
const url = s3.getSignedUrl('getObject', params) // or getSignedUrlPromise

In V2, you had two options to generate pre-signed URLs - synchronously and asynchronously.

In V3, there's only one option - asynchronous one. Moreover, it requires you to import @aws-sdk/s3-request-presigner package and call getSignedUrl using the client and the Command object.

Lambda

Invoking functions

Even though invoking functions from functions is considered as an antipattern by some, I believe that's not the case if you know what you're doing.

import { Lambda } from 'aws-sdk'
const lambda = new Lambda()
const params = {
FunctionName: 'Lambda_B',
InvocationType: 'RequestResponse',
LogType: 'Tail',
Payload: '{ "name" : "Alex" }',
}
await lambda.invoke(params)
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda'
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda'

In V2, invoking Lambda asynchronously or synchronously required just the FunctionName parameters. Optional params like LogType: 'Tail' are helpful if we want to forward up to 4KB of logs from the invoked Lambda to the caller.

In V3, the signature is almost identical. The difference is in the Payload parameter. While V2 accepted string, V3 accepts only Uint8Array. Since in Node.js Buffer instances are also Uint8Array instances, we can use them instead.

Other notes / problems

Using SSO-based credentials

Up to version 3.7.0, using SSO credentials was impossible. The workaround was to copy & paste credentials provided by the SSO as environment variables. Luckily, this issue is now solved.

Lack of XRay support

As I'm writing this, AWS SDK V3 is not working correctly with X-Ray, which is a showstopper for many of us. This is due to the new middleware architecture, which is incompatible with how the X-Ray SDK hooks into the version 2.x. But, there's a pull request already reviewed as ☑️, waiting to be merged

Lack of ChainableTemporaryCredentials

Currently, AWS SDK V3 is not supporting this. This is especially useful in multi-tenant SaaS environments where your Lambdas/Containers/VMs have to assume a dynamic role-based on runtime input/context.

Reusing HTTP connections

In V2, there was this famous Keep-Alive trick which forced AWS SDK to reuse HTTP connections. This reduced the number of HTTP handshakes performed and improved overall Lambda performance. In V3, this is happening automatically.


-- Written by Rafal Wilinski. Founder of Dynobase - Professional GUI Client for DynamoDB


GithubTwitterInstagram