Deprecated AWS 3rd Party Services

Howdy,

With 3rd party services being deprecated August 1, 2023 I wanted to reopen this conversation so the community can best understand how we should migrate from 3rd party services in MongoDB Realm, specifically AWS S3 in this case.

The conversation ended with the 3rd party services being extended an additional year and a community member @Adam_Holt kindly sharing an implementation to on how to use AWS S3 successfully and efficiently without 3rd party services.

Given that timeline, I was under the impression that MongoDB would have published some tutorial or made some comments by now as to the best way to use AWS services going forward. Perhaps I missed this in my search.

My question, what is the recommended approach to getObjects and putObjects to AWS S3? Without the 3rd party services and @Adam_Holt’s suggestion, I am only aware of 1 other solution:

Adam’s Solution

Realm function

exports = async function (args) {
  const bucket = "my-s3-bucket";

  const S3 = require("aws-sdk/clients/s3");
  const s3 = new S3({
    accessKeyId: context.values.get("awsAccessKeyId"),
    secretAccessKey: context.values.get("awsSecretAccessKey"),
    region: "ap-southeast-2"
  });

  const presignedUrl = await s3.getSignedUrlPromise("putObject", {
    Bucket: bucket,
    Key: args.Key,
    ContentType: args.ContentType,
    // Duration of the lifetime of the signed url, in milliseconds
    Expires: 900000
  });
  return presignedUrl;
};

Frontend JS code

import { realmUser } from "../../main";
const axios = require("axios");

export default class UploadFile {
  async handleFileUpload(file) {
    const returnObj = {
      s3Path: "",
      s3: {},
    };
    if (file.size > 26214400) {
      alert("No files over 25 MB supported");
      return false;
    }

    const key = `files/${file.name}`;
    returnObj.s3Path = key;
    // AWS S3 Request
    const args = {
      ContentType: file.type,
      Key: key,
    };

    try {
      const presignedUrl = await this.getPresignedS3URL(args);
      const options = {
        headers: {
          "Content-Type": file.type,
        },
      };
      // Saves the file to S3
      await axios.put(presignedUrl, file, options);
      returnObj.s3.key = key;
    } catch (error) {
      console.log(error);
    }

    // Return the data back to a componenet
    return returnObj;
  }

  async getPresignedS3URL(args) {
    return new Promise((resolve, reject) => {
      realmUser.functions
        .uploadTestBuyFile(args)
        .then((doc) => {
          resolve(doc);
        })
        .catch((err) => {
          reject(err);
        });
    });
  }
}

Legacy Solution (This is remarkably slower than the MongoDB 3rd Party Services)

exports = async function(newId, newPicture, bucket) {
  
  const S3 = require('aws-sdk/clients/s3');
  const s3 = new S3({
    accessKeyId: context.values.get("AWS_ACCESS_KEY"),
    secretAccessKey: context.values.get("AWS_SECRET_ACCESS_KEY_LINKED"),
    region: "region",
  })
    
  const putResult = await s3.putObject({
    Bucket: bucket,
    Key: newId,
    ContentType: "image/jpeg",
    Body: Buffer.from(newPicture, 'base64'),
    ContentEncoding: 'base64'
  }).promise();

}

Thanks for your help! @henna.s tagging you as you were involved in the prior conversations.

Legacy Deprecated 3rd Party Services Conversation

Hello @Jason_Tulloch1 ,

Its been a long while :smiley: How are you? Thank you so much for raising your concern.

Please allow me some time to talk to teams internally and I will get back to you as soon as I can.

Cheers, :performing_arts:
henna

Hi @Jason_Tulloch1,

As per my discussion with the App Services Team, here are some guidelines on how to move from third-party services to npm modules. For uploading images, you may have to be mindful of the image size as the memory limit imposed on function runtime may become a limiting factor.

Unfortunately, at this time, there isn’t any documentation available on uploading images to AWS S3.

Thanks,

@Jason_Tulloch1

I had to move my own application over this weekend. It was a long weekend here in NZ and I figured it was a good time to do it before the August cut off. The one I moved previously was for a client.

As AWS v3 SDK is now recommended over v2, I thought I would try this as it was likely to be much lighter than the v2 package as every service is its own package.

Here are some examples from code I have put together which should give you a good idea of how you can use v3. It seems to be working really well.

The one thing I would avoid is trying to pull data into the function’s memory. That is where you will get bad performance. So no uploading and downloading from S3. Instead, use presigned URLs and do the downloading/uploading in the browser from the client side.

functions/package.json

{
  "name": "functions",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "@aws-sdk/client-lambda": "^3.369.0",
    "@aws-sdk/client-s3": "^3.369.0",
    "@aws-sdk/s3-request-presigner": "^3.369.0",
    "@aws-sdk/client-ses": "^3.369.0"
  },
  "author": "",
  "license": "ISC"
}

functions/aws_getConfig.js

exports = async function () {
  const AWS_CONFIG = {
    credentials: {
      accessKeyId: context.values.get('AWS_ACCESS_ID'),
      secretAccessKey: context.values.get('AWS_SECRET_KEY'),
    },
    region: 'ap-southeast-2',
  }
  return AWS_CONFIG
}

functions/s3_put.js

exports = async function (key) {
  const AWS_CONFIG = await context.functions.execute('aws_getConfig')
  const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
  const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
  const s3Service = new S3Client(AWS_CONFIG)

  const presignedUrl = await getSignedUrl(
    s3Service,
    new PutObjectCommand({
      Bucket: 'BUCKET_NAME',
      Key: key,
      Method: 'PUT',
      ExpirationMS: 120000,
    })
  )
  return presignedUrl
}

Then you can use something like this in your client code.

const preSigned = await s3.uploadFile(fileName) // This is calling your mongo app function
await axios.put(presignedUrl, file, {
  headers: {
    'Content-Type': file.type
  },
})

functions/s3_get.js

exports = async function (key) {
  const AWS_CONFIG = await context.functions.execute('aws_getConfig')
  const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3')
  const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
  const s3Service = new S3Client(AWS_CONFIG)
  
  const presignedUrl = await getSignedUrl(
    s3Service,
    new GetObjectCommand({
      Bucket: 'BUCKET',
      Key: key,
      Method: 'GET',
      ExpirationMS: 120000,
    })
  )

  return presignedUrl
}

Run Lambda in a function

const AWS_CONFIG = await context.functions.execute('aws_getConfig')
const { InvokeCommand, LambdaClient } = require('@aws-sdk/client-lambda')
const lambda = new LambdaClient(AWS_CONFIG)

const lambdaPayload = {
  foo: 'bar',
}

const lambdaCommand = new InvokeCommand({
  FunctionName: 'FUNC_NAME',
  Payload: JSON.stringify(lambdaPayload)
})

const lambdaResult = await lambda.send(lambdaCommand)
const result = EJSON.parse(Buffer.from(lambdaResult.Payload).toString())
return result

Send an email via SES

const AWS_CONFIG = await context.functions.execute('aws_getConfig')
const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses')
const ses = new SESClient(AWS_CONFIG)
const sendEmailCommand = new SendEmailCommand({
  Source: `${settings.CompanyName}<noreply@rapido.co.nz>`,
  Destination: {
    ToAddresses: [user.email],
    CcAddresses: [settings.Orders_EmailCC]
  },
  Message: {
    Body: {
      Html: {
        Charset: 'UTF-8',
        Data: email,
      },
    },
    Subject: {
      Charset: 'UTF-8',
      Data: subject,
    },
  },
})
const send = await ses.send(sendEmailCommand)
1 Like

Thanks @Adam_Holt for sharing these examples with the community!

@Jason_Tulloch1 I’ve been updating some of the other threads on this - the team has decided to extend the deprecation timeline for 3rd party services from Aug 1st, 2023 to November 1, 2024. So there will be no impact to your application on Aug 1st. We will be updating the banners in product as well as in documentation, this week, to reflect these changes.

2 Likes

Thanks @Adam_Holt

A quick note for you and others:

As of the time of this message, the only “officially” supported version 3 of the AWS SDK is 3.100.0 (more recent versions, at the very least, do not work when trying to send messages to SQS)