Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: upgrade AWS SDK to v3 #83

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

gbryan
Copy link
Contributor

@gbryan gbryan commented Aug 19, 2022

Ticket: https://app.asana.com/0/1200444444072189/1202815880040240

We will not yet merge this because we cannot upgrade Beyonce's AWS SDK to v3 without also upgrading Vault's. Timeboxing Vault's AWS SDK to v3 to 30 minutes indicated that it isn't as quick enough a task that we can take it on right now. We can pick that up (and also merge + test E2E this PR) in the future within one of our teams' tech debt budgets.


This PR upgrades Beyonce from v2 to v3 of the AWS SDK, which involves breaking changes: clients need to instantiate Beyonce with a DynamoDB instance from @aws-sdk/client-dynamodb v3 instead of aws-sdk v2.

Reviewing commit-by-commit will be easiest.

The original motivation for this upgrade was to experiment with the new error responses from DynamoDB when encountering a condition check failure while executing a transaction, to see if the new error messages provide enough detail to determine which condition check failed (so we could distinguish the reason(s) in the case that there were to be multiple condition checks). Related ticket. It turns out that even the new v3 error responses do not contain such detail. For example, here's an error:

    TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]
        at deserializeAws_json1_0TransactionCanceledExceptionResponse (/Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/client-dynamodb/dist-cjs/protocols/Aws_json1_0.js:3072:23)
        at deserializeAws_json1_0TransactWriteItemsCommandError (/Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/client-dynamodb/dist-cjs/protocols/Aws_json1_0.js:2380:25)
        at processTicksAndRejections (node:internal/process/task_queues:96:5)
        at /Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
        at /Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/middleware-signing/dist-cjs/middleware.js:11:20
        at StandardRetryStrategy.retry (/Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/middleware-retry/dist-cjs/StandardRetryStrategy.js:51:46)
        at /Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
        at /Users/bryangaston/workspace/gingerio/beyonce/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/TransactWriteCommand.js:36:26 {
      '$fault': 'client',
      '$metadata': {
        httpStatusCode: 400,
        requestId: 'e3e60378-8cbf-44b4-9a8c-15869adec843',
        extendedRequestId: undefined,
        cfId: undefined,
        attempts: 1,
        totalRetryDelay: 0
      },
      CancellationReasons: [
        { Code: 'None', Item: undefined, Message: undefined },
        {
          Code: 'ConditionalCheckFailed',
          Item: undefined,
          Message: 'The conditional request failed'
        }
      ],
      __type: 'com.amazonaws.dynamodb.v20120810#TransactionCanceledException'
    }

@gbryan gbryan self-assigned this Aug 19, 2022
@@ -1,14 +1,14 @@
# Needed for DynamoDB Local, which is used for unit tests
FROM openjdk:15-alpine3.11
# Java JDK is needed for DynamoDB Local, which is used for unit tests
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get AWS SDK v3 support for the local DynamoDB, we have to upgrade jest-dynamodb, which necessitates upgrading to a newer version of Nodejs, which necessitates switching from the deprecated base Docker image we were using to this one.

@@ -12,8 +12,10 @@ module.exports = async () => {
port,
clientConfig: {
endpoint,
sslEnabled: false,
region: "local",
credentials: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my testing, I got prompts to fetch new AWS SSO credentials unless I provided dummy credentials for the local DynamoDB.

@@ -1,6 +1,6 @@
{
"name": "@ginger.io/beyonce",
"version": "0.0.66",
"version": "0.0.67",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has breaking changes, but since we haven't hit version 1, I'm assuming we're ok with just continuing to bump the version like this instead of going to version 1.0.0 now. LMK if you'd prefer otherwise!

@@ -12,8 +12,9 @@
"./src"
],
"dependencies": {
"@aws-sdk/client-dynamodb": "3.113.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pinning to the same versions that @shelf/jest-dynamodb uses because I was encountering errors with that library before doing so.

private jayz?: JayZ
private consistentReads: boolean

constructor(private table: Table<string, string>, dynamo: DynamoDB, options: Options = {}) {
this.client = new DynamoDB.DocumentClient({ service: dynamo })
this.client = DynamoDBDocumentClient.from(dynamo, {
marshallOptions: { removeUndefinedValues: true }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attempting to write keys with undefined values results in errors unless specifying this option.

@@ -29,6 +30,8 @@ export async function batchWriteItems<T extends TaggedModel>(
deleteItems.forEach((key) => {
requests.push({
DeleteRequest: {
// @ts-ignore The type is incorrect for `Key`. It expects Record<string, AttributeValue>, but since we're using
// a DynamoDBDocumentClient, it needs to be Record<string, string> instead.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writes fail if I pass an object in the format Record<string, AttributeValue>. The type in the underlying library is wrong.

const unprocessedPuts: T[] = []
const unprocessedDeleteKeys = new UnprocessedKeyCollector(table, deleteItems)

if (results.UnprocessedItems && results.UnprocessedItems[tableName]) {
results.UnprocessedItems[tableName].forEach(({ PutRequest, DeleteRequest }) => {
if (PutRequest) {
unprocessedPuts.push(PutRequest.Item as T)
} else if (DeleteRequest) {
} else if (DeleteRequest && DeleteRequest.Key) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeleteRequest.Key is now undefined.

@@ -33,6 +34,8 @@ export async function transactWriteItems<T extends TaggedModel>(params: Transact
requests.push({
Delete: {
TableName: table.tableName,
// @ts-ignore The type is incorrect for `Key`. It expects Record<string, AttributeValue>, but since we're using
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: the type in the underlying lib is wrong.

} else {
throw e
if (e && e.CancellationReasons) {
throw new Error(`${e.message}. Cancellation Reasons: ${JSON.stringify(e.CancellationReasons)}`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice that we can skip all the parsing from the httpResponse now.

}
}

/**
* The DynamoDB v3 lib returns items with values that are UInt8Array instead of Buffer, so this function
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response format appears to have changed from Buffer to UInt8Array, so we're converting to keep the expected format for clients.

ConsistentRead: true
})
expect(mockGet).toHaveBeenCalledWith(
expect.objectContaining({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use objectContaining now since there are various other properties on the argument passed to send() now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant