serverless Archives - ProdSens.live https://prodsens.live/tag/serverless/ News for Project Managers - PMI Sun, 19 May 2024 19:20:40 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://prodsens.live/wp-content/uploads/2022/09/prod.png serverless Archives - ProdSens.live https://prodsens.live/tag/serverless/ 32 32 OpenAPI (Swagger docs) integration with serverless framework | Node.js Express.js Typescript https://prodsens.live/2024/05/19/openapi-swagger-docs-integration-with-serverless-framework-node-js-express-js-typescript/?utm_source=rss&utm_medium=rss&utm_campaign=openapi-swagger-docs-integration-with-serverless-framework-node-js-express-js-typescript https://prodsens.live/2024/05/19/openapi-swagger-docs-integration-with-serverless-framework-node-js-express-js-typescript/#respond Sun, 19 May 2024 19:20:40 +0000 https://prodsens.live/2024/05/19/openapi-swagger-docs-integration-with-serverless-framework-node-js-express-js-typescript/ openapi-(swagger-docs)-integration-with-serverless-framework-|-nodejs-express.js-typescript

Introduction: OpenAPI formerly known as swagger docs is a specification. It’s a standardised way to describe web APIs…

The post OpenAPI (Swagger docs) integration with serverless framework | Node.js Express.js Typescript appeared first on ProdSens.live.

]]>
openapi-(swagger-docs)-integration-with-serverless-framework-|-nodejs-express.js-typescript

Introduction:

OpenAPI formerly known as swagger docs is a specification. It’s a standardised way to describe web APIs using a machine-readable format, typically YAML or JSON. OpenAPI docs could also be written in line as comments in javascript/typescript files. This format outlines the different endpoints, what kind of data they expect (request body), what kind of data they return (response), and other details that are crucial for developers and tools (eg: postman) to understand and interact with your API effectively. 

Serverless framework lets you build and deploy applications without managing servers. You write code for specific functions that execute in response to events (like an API request). The cloud provider handles server provisioning, scaling, and maintenance, allowing you to focus on core functionality and benefit from pay-per-use pricing.

Setting up swagger/OpenAPI with serverless:

Install the following dependencies
npm i swagger-jsdoc with this dependency you will be able to extract swagger doc from comments in javascript files. 

npm i swagger-ui-express this serves interactive swagger ui.

sample swagger ui

Following is a utility function that sets up the swagger configuration, the function accepts express app as the input

sawgger setup with serverless framework

swagger docs extracts the api definitions from the .js files mentioned in the apis array.

swagger and serverless x-forwarded-prefix

Swagger needs the original URL to correctly generate the api documentation, but the api gateway (proxy) modifies the request URL and store a copy of original URL on the “x-forwarded-prefix” header and with this middleware the original URL gets assigned to req.originalUrl.

setting up swagger (openAPI) with serverless framework

A sample swagger doc

Sample swagger doc

Result

final result swagger doc integration with serverless framework

I had trouble setting up swagger docs with AWS and i hope this article helps you out.
Thank you for reading 🙂
Abd038

Socials:
https://www.linkedin.com/in/abdul-mohammad-a567b2183/
https://twitter.com/iam_abd038
https://github.com/iam-abdul

The post OpenAPI (Swagger docs) integration with serverless framework | Node.js Express.js Typescript appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/19/openapi-swagger-docs-integration-with-serverless-framework-node-js-express-js-typescript/feed/ 0
Navigating the Clouds: A Comprehensive Guide to Modern Cloud Infrastructures https://prodsens.live/2024/05/19/navigating-the-clouds-a-comprehensive-guide-to-modern-cloud-infrastructures/?utm_source=rss&utm_medium=rss&utm_campaign=navigating-the-clouds-a-comprehensive-guide-to-modern-cloud-infrastructures https://prodsens.live/2024/05/19/navigating-the-clouds-a-comprehensive-guide-to-modern-cloud-infrastructures/#respond Sun, 19 May 2024 13:20:23 +0000 https://prodsens.live/2024/05/19/navigating-the-clouds-a-comprehensive-guide-to-modern-cloud-infrastructures/ navigating-the-clouds:-a-comprehensive-guide-to-modern-cloud-infrastructures

Introduction: ​ As a full-stack developer, understanding cloud architecture is crucial in today’s digital age, where cloud computing…

The post Navigating the Clouds: A Comprehensive Guide to Modern Cloud Infrastructures appeared first on ProdSens.live.

]]>
navigating-the-clouds:-a-comprehensive-guide-to-modern-cloud-infrastructures

Introduction:

​ As a full-stack developer, understanding cloud architecture is crucial in today’s digital age, where cloud computing serves as the backbone of the tech industry. This technology supports everything from small startups to global enterprises. Mastering cloud architecture goes beyond knowing the various components; it involves designing, deploying, and managing these systems efficiently to fully leverage their potential.

In this article, we will delve into the intricate world of cloud architectural design, exploring key patterns from traditional client-server models to advanced serverless frameworks. Whether you aim to optimize your applications for better performance or ensure high scalability and availability, the insights provided here will guide you through the essential architectures and best practices in modern cloud computing.

Cloud architecture is a conceptual model that encompasses the necessary components and subcomponents for cloud computing. These typically include a front-end platform, back-end platforms, a cloud-based delivery system, and a network designed to deliver computing services over the Internet.

Let’s explore the critical components and design patterns that form the foundation of effective cloud architecture.

Key Components of Cloud Architecture:

  • Front-End Platform (Client Side): This is what the end-user interacts with, typically involving web browsers or mobile applications.
  • Back-End Platform (Server Side): This includes servers, storage, and databases that manage the data and business logic of the application.
  • Cloud-Based Delivery Models: These models include infrastructure as a service (IaaS), platform as a service (PaaS), and software as a service (SaaS), each offering different levels of control, flexibility, and management.
  • Network: This includes the Internet or intranet, enabling communication between the front-end and back-end platforms.

The benefits of cloud architectures include the ability to easily scale resources to meet demand, reduce or eliminate capital expenditure on hardware and facilities, ensure services are always available, and safeguard data against local failures or disasters.

1. Client-Server Architecture:

The client-server architecture is a model where client applications request services from servers, which respond with the requested information or actions. This fundamental architecture underpins many web and network applications. It simplifies network management and centralizes data storage and processing but requires robust server management to avoid bottlenecks.

How It Works:

  • Request-Response Cycle: The client sends a request to the server for specific information or actions. The server processes this request and sends back a response. This cycle repeats as necessary to fulfill the client’s needs.
  • Centralized Management: Data and services are centralized on the server, making it easier to manage, update, and secure the resources. This centralization also simplifies data backups and disaster recovery.
  • Scalability Challenges: While centralization simplifies management, it can lead to scalability issues. As the number of clients increases, the server must handle more requests, which can create bottlenecks. Load balancing and server clustering are common solutions to address these challenges.

Advantages:

  • Centralized Data Storage: All data is stored on the server, ensuring consistency and easier management.
  • Simplified Network Management: With centralized control, network management and security enforcement become more straightforward.
  • Ease of Maintenance: Updates and maintenance can be performed on the server without needing to modify the client-side applications.

Disadvantages:

  • Server Dependency: If the server fails, clients cannot access the requested services or data, leading to potential downtime.
  • Scalability Issues: High traffic can overwhelm the server, causing performance degradation unless proper load balancing and scaling strategies are implemented.
  • Network Latency: The performance of client-server applications can be affected by network latency, especially if clients are geographically dispersed.

Use Cases:

  • Basic Web Servers:

A common use case for client-server architecture is web hosting. Websites are hosted on servers, and web browsers (clients) request web pages, which the server then delivers.

  • Example: When you visit a website, your browser sends a request to the web server hosting the site. The server processes this request, retrieves the necessary web page, and sends it back to your browser to be displayed.

    • Email Services:

Email applications use a client-server model where email clients (e.g., Outlook, Gmail app) request emails from email servers.

  • Example: When you check your email, your email client sends a request to the email server. The server processes this request, retrieves your emails, and sends them back to the client for you to read.

    • Online Banking:

Online banking platforms use client-server architecture to allow users to manage their accounts and perform transactions securely.

  • Example: When you log into your online banking account, your client application sends a request to the bank’s server. The server verifies your credentials and provides access to your account information and services.

The post Navigating the Clouds: A Comprehensive Guide to Modern Cloud Infrastructures appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/19/navigating-the-clouds-a-comprehensive-guide-to-modern-cloud-infrastructures/feed/ 0
AWS Cloud Cost Optimization https://prodsens.live/2024/05/19/aws-cloud-cost-optimization/?utm_source=rss&utm_medium=rss&utm_campaign=aws-cloud-cost-optimization https://prodsens.live/2024/05/19/aws-cloud-cost-optimization/#respond Sun, 19 May 2024 01:20:43 +0000 https://prodsens.live/2024/05/19/aws-cloud-cost-optimization/ aws-cloud-cost-optimization

Challenge: Reduce the AWS Cloud Operating cost without compromising any existing feature Steps: Switch to 8×5 from 24×7…

The post AWS Cloud Cost Optimization appeared first on ProdSens.live.

]]>
aws-cloud-cost-optimization

Challenge: Reduce the AWS Cloud Operating cost without compromising any existing feature

Steps:

  1. Switch to 8×5 from 24×7 in Dev and QA Environments​
  2. Use the correct size of Serves in Dev and QA based on the load​
  3. Avoid using same server configuration like Prod in Dev and QA​
  4. Create Lifecycle rules and auto-shutdown feature for Servers ( SQL Analytics, SageMaker, S3 …. )​
  5. Minimise Operational Maintenance activities

The post AWS Cloud Cost Optimization appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/19/aws-cloud-cost-optimization/feed/ 0
AWS Credentials for Serverless https://prodsens.live/2024/04/25/aws-credentials-for-serverless/?utm_source=rss&utm_medium=rss&utm_campaign=aws-credentials-for-serverless https://prodsens.live/2024/04/25/aws-credentials-for-serverless/#respond Thu, 25 Apr 2024 14:20:27 +0000 https://prodsens.live/2024/04/25/aws-credentials-for-serverless/ aws-credentials-for-serverless

AWS is a zero-trust platform1. That is, every call to AWS must provide credentials so that the caller…

The post AWS Credentials for Serverless appeared first on ProdSens.live.

]]>
aws-credentials-for-serverless

AWS is a zero-trust platform1. That is, every call to AWS must provide credentials so that the caller can be validated and her authorization checked. How one manages these credentials will vary depending on the execution environment. A developer, who gets his workstation set up with his own AWS credentials, will often find that the application he is building cannot (and should not) consume credentials in the same way. Why do these differences exist? And what should he do to manage credentials?

For credentials on your workstation, AWS recommends using IAM Identity Center SSO. This lets you verify your identity (often with a standard, non-AWS identity provider like Google or Okta), and then that user can assume an IAM role to provide a set of temporary credentials. This works well and is fairly secure, especially if your identity provider is set up with multi-factor authentication (MFA). Because the AWS credentials are short-lived, even if they leak out, they expire quickly thus limiting exposure. Why can’t we take this approach with the applications we are building?

We want to have the application assume a role and pick up short-term credentials. However, we can’t use the workstation approach because we need user authentication (SSO/MFA) to establish the user, and that’s not possible at the application’s runtime. To get out of this jam, we can rely on the fact that all our application runtimes are serverless and will happen within an AWS service (in our case Lambda or Fargate). This establishes sufficient trust such that we can assign the execution environment a role and let it obtain short-term credentials.

In this article, I want to examine how your application running in either Lambda or Fargate ought to get its AWS credentials. We’ll discuss how the AWS SDKs use credential provider chains to establish precedence and one corner case I found myself in. Let’s dig in.

Credential Sources

As mentioned earlier, you can provide credentials to your application in several ways including (but not limited to) environment variables, config/credentials files, SSO through IAM Identity Center, instance metadata, and (don’t do this) directly from code. Irrespective of which method you choose, you can allow the AWS SDK to automatically grab credentials via its built-in “credentials provider.”

The mechanism for selecting the credential type is called the “credential provider” and the built-in precedence (i.e., the order in which it checks credential sources) is called the “credential provider chain.” This is language agnostic. Per AWS documentation, “All the AWS SDKs have a series of places (or sources) that they check to get valid credentials to use to make a request to an AWS service.” And, once they locate any credentials, the chain stops and the credentials are used.

For the NodeJS SDK, that precedence is generally:

  1. Explicit credentials in code (again, please don’t do this)
  2. Environment Variables
  3. Shared config/credentials file
  4. Task IAM Role for ECS/Fargate
  5. Instance Metadata Service (IMDS) for EC2

So, we can pass in credentials in many ways. Why should we choose one over another? Each approach varies in terms of security and ease of use. Fortunately, AWS allows us to easily set up our credentials without compromising security. We are going to focus on two recommended approaches: environment variables (for Lambda) and the task IAM role (for Fargate)

Environment Variable Credentials

Credentials in environment variables are, perhaps, the easiest way to configure your AWS SDK. They are near the top of the precedence list and will get scooped up automatically when you instantiate your SDK. For example, if you set the following environment variables in your runtime:

export AWS_ACCESS_KEY_ID="AKIA1234567890"
export AWS_SECRET_ACCESS_KEY="ABCDEFGH12345678"

Then when you instantiate your AWS SDK, these credentials will get loaded automatically, like so:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
const client = new DynamoDBClient({ region: 'us-east-1' }); // Loads env credentials

Note that the AWS_ACCESS_KEY_ID begins with “AKIA”. This signifies that this is a long-term access key with no expiration. These types of keys are attached to an IAM user or, if you are reckless, the AWS account root user2.

Alternatively, you may run across AWS credentials that look like the following:

AWS_ACCESS_KEY_ID=ASIA1234567890
AWS_SECRET_ACCESS_KEY=ABCDEFGH12345678
AWS_SESSION_TOKEN=XYZ+ReallyLongString==

These credentials are short-lived. You can tell this both by the presence of the AWS_SESSION_TOKEN and that the AWS_ACCESS_KEY_ID begins with “ASIA” instead of “AKIA”.

When you use a credential provider, it consumes the Access Key, Secret, and Session Token. These tokens can be set to expire anywhere from 15 minutes to 12 hours from issuance. This would be a drag if you had to repeatedly go fetch these short-lived tokens and save them so your application can use them. Fortunately, you don’t have to. Both Lambda and ECS offer built-in mechanics to provide your application with short-term credentials. Let’s start with Lambda.

Using Credentials in Lambda

Add the following line to one of your Lambdas:

console.log(process.env);

And you’ll see AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. How did they get there? AWS added them for you using the (required) IAM role attached to your Lambda. During initialization, AWS calls out to their Secure Token Service (STS), obtains short-term credentials, and then conveniently injects those credentials into your Lambda’s environment variables.

Lambdas are special in this regard. You (or the credentials provider) don’t have to do anything extra to fetch credentials, they are just there for you to use. Why?

Lambdas are short-lived. Even under constant use, every hour or so they are automatically recycled. This means that a single short-lived token can serve the Lambda that is using it; no re-fetching of tokens is necessary. For example, if AWS sets Lambdas to last no more than an hour before being decommissioned, it can set the expiration for the access token to just over 60 minutes and the application using the token will never need to fetch another.

Having your credentials provider automatically find and use the credentials in Lambda’s environment variables is both the recommended and easiest approach. This is a true win/win.

Using Credentials in Fargate

ECS Fargate shares many traits with Lambda: they’re both managed by AWS (in neither case are we taking care of the underlying servers), they scale up and down automatically, and each can have an IAM role that provides permissions for the application’s runtime.

However, Fargate containers don’t automatically recycle. They are relatively long-lived when compared to Lambda and can easily live longer than the maximum STS token expiration. This means the method used by Lambda to inject the STS tokens into the runtime environment won’t work.

Instead, you can use the–optional but recommended–Task Role ARN property of your ECS task definition to specify the permissions you would like your task to have. Then your credentials provider can assume this role to obtain short-term credentials it can use. It manages this for you and you don’t have to do anything but set the TaskRoleArn in your task definition.

Why You Should Know This

The AWS SDK’s credentials provider doesn’t know “I’m in a Lambda” or “I’m in Fargate.” When invoked, the SDK will use the default credentials provider to step through a chain of locations to look for credentials and it will stop as soon as it finds one. This means things often “just work.” But, it also means you can short-circuit the precedence chain if you are not careful (or you can do it purposefully; I’ll give an example later).

If you are using Lambda, and you new up an SDK client like this:

const client = new DynamoDBClient({
  region: 'us-east-1',
  credentials: {
    accessKeyId: 'ABC', // Don't do this
    seretAccessKey: '123', // Don't do this, either
  },
});

your credentials provider will never check the environment variables for credentials and will run with what you gave it.

Likewise, in Fargate, if you either pass in direct credentials or set environment variables of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, your credentials provider will never use your TaskRoleArn. This can be confusing if you are not used to it.

Breaking the Chain on Purpose

I was working with a client on a container migration project, where they needed to move their container workloads from Kubernetes (K8) on EC2 over to Fargate on ECS. At one point during the transition, the same container needed to be simultaneously running in both places. I knew I wanted to use the TaskRoleArn in Fargate, but that would not fly in the K8 deployment as it would grab the credentials from the EC2 instance on which it ran. And, since that EC2 instance served many disparate K8 pods, it was a poor3 place to manage the runtime permissions of the containers underneath it.

The K8 configuration had environment variables set to long-term credentials for an IAM user. At first, the ECS task definition just used the same credentials (from env vars). Then, we created a dedicated IAM role for the task and attached it to the definition as a TaskRoleArn. OK, time for a quick quiz:

What happens now? The ECS container will:
A) Use the IAM role from TaskRoleArn.
B) Use the environment variable credentials.
C) Throw a ConflictingCredentialsError.

The correct answer is B. As long as those environment variable credentials are present, the credentials provider will stop looking after discovering them. During the migration, we used this to our advantage as we kept the code the same and just modified the configuration based on the destination (environment variable credentials in K8, none in Fargate). Eventually, we were only using the TaskRoleArn and we could retire those long-term credentials and the environment variables that surfaced them.

What Can Go Wrong?

Long-term credentials pose a real risk of leaking. AWS advises its users to take advantage of SSO and IAM roles for their user and application runtimes, respectively. I know an engineer who inadvertently checked in a hard-coded AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY into a public GitHub repository. Within minutes, they had been scraped, and expensive BitCoin miners were deployed in far-away regions of his company’s AWS account (kudos to AWS for expunging those actions from their AWS bill the following month).

The engineer had thought the repository was private. However, the fundamental issue was using hard-coded, long-lived credentials in the first place. Using fully managed, serverless architectures like Lambda and Fargate along with role-based, short-term credentials, you can avoid this kind of headache.

Further Reading

AWS Documentation: Setting credentials in Node.js
AWS CLI User Guide: Configuration and credential file settings
Amazon ECS Developer Guide: Task IAM role
Ownership Matters: Zero Trust Serverless on AWS
Gurjot Singh: AWS Access Keys – AKIA vs ASIA
Nick Jones: AWS Access Keys – A Reference

  1. I have written on zero trust here↩

  2. Never give programmatic access to your root user. ↩

  3. Least Privilege would be off the table. ↩

The post AWS Credentials for Serverless appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/04/25/aws-credentials-for-serverless/feed/ 0
Image Labeling with Amazon Rekognition https://prodsens.live/2024/02/21/image-labeling-with-amazon-rekognition/?utm_source=rss&utm_medium=rss&utm_campaign=image-labeling-with-amazon-rekognition https://prodsens.live/2024/02/21/image-labeling-with-amazon-rekognition/#respond Wed, 21 Feb 2024 12:20:23 +0000 https://prodsens.live/2024/02/21/image-labeling-with-amazon-rekognition/ image-labeling-with-amazon-rekognition

Amazon Rekognition facilitates object and scene detection in images, offering a secure, stateless API that returns a list…

The post Image Labeling with Amazon Rekognition appeared first on ProdSens.live.

]]>
image-labeling-with-amazon-rekognition

Amazon Rekognition facilitates object and scene detection in images, offering a secure, stateless API that returns a list of related labels along with confidence levels.

In this tutorial, you’ll build a serverless system to perform object detection upon image uploads to an Amazon S3 bucket. AWS Lambda will handle the processing logic, while Amazon DynamoDB will serve as the storage solution for the label detection results.

Object Detection Context and Limitations

Object and Scene Detection involves identifying objects and their context within an image. Object Detection locates specific instances of objects like humans, cars, buildings, etc. In Amazon Rekognition, Object Detection is akin to Image Labeling, extracting semantic labels from images. This approach also lends itself to scene detection, identifying both individual objects and overall scene attributes, such as “person” or “beach”.

In AWS, Object Detection with Amazon Rekognition involves utilizing its DetectLabels API. You can input the image as either a binary string or an S3 Object reference. Submitting images as S3 Objects offers advantages such as avoiding redundant uploads and supporting images up to 15MB in size, compared to the 5MB limit.

The response typically follows a JSON structure similar to the following:

{
    "Labels": [
        {
            "Confidence": 97,
            "Name": "Person"
        },
        {
            "Confidence": 96,
            "Name": "Animal"
        },
        ...
    ]
}

The API provides an ordered list of labels, ranked by confidence level, starting from the highest.

The quantity of labels returned is determined by two parameters:

  1. MaxLabels: This sets the maximum number of labels returned by the API.
  2. MinConfidence: Labels with a confidence score below this threshold will not be included in the response.

It’s crucial to note that setting low values for MaxLabels alongside high values for MinConfidence could result in empty responses.

Throughout the tutorial, we’ll use an S3 bucket to store images, leveraging the API to extract labels from each newly uploaded image. We’ll store each image-label pair in a DynamoDB table.

Let’s start

1) create DynamoDB table named “images”

table

2) Create S3 bucket
Choose a unique name for the bucket, I will use the name “demo21-2”
Make sure to select ACLs Enabled:
Create a folder in the bucket and name it “images”

3) Create a Lambda function as follows:
Choose “Use a blueprint” when creating the function.
For the blueprint name, choose “Use Rekognition to detect faces”

lambda

You will need to give the lambda function permissions to access the following services:

  • “CloudWatch” to put logs
  • “DynamoDB” to put, update, describe item
  • “Rekognition” to detect labels and faces

So you will create a role with the following IAM policies, and assume the role by the lambda function.

Basic execution role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}

Lambda policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeStream",
                "dynamodb:GetRecords",
                "dynamodb:GetShardIterator",
                "dynamodb:ListStreams"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:909737842772:table/images",
            "Effect": "Allow"
        },
        {
            "Action": [
                "rekognition:DetectLabels",
                "rekognition:DetectFaces"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

For the S3 trigger section and enter the following, accepting the defaults for values not specified:

s3

You are now ready to test the function to see if it’s successfully been triggered and called AWS Rekognition
Upload a picture to the folder “images” in your bucket
Go check the CloudWatch log group for your functions, you should see a log similar to this one below:

log

Implementing the Object Detection Logic
In the Code source section, double-click the lambda_function.py file, and replace the code with the following:

import boto3, urllib.parse

rekognition = boto3.client('rekognition', 'us-west-2')
table = boto3.resource('dynamodb').Table('images')

def detect_labels(bucket, key):
    response = rekognition.detect_labels(
        Image={"S3Object": {"Bucket": bucket, "Name": key}},
        MaxLabels=10,
        MinConfidence=80,
    )

    labels = [label_prediction['Name'] for label_prediction in response['Labels']]

    table.put_item(Item={
        'PK': key,
        'Labels': labels,
    })

    return response


def lambda_handler(event, context):
    data = event['Records'][0]['s3']
    bucket = data['bucket']['name']
    key = urllib.parse.unquote_plus(data['object']['key'])
    try:
        response = detect_labels(bucket, key)
        print(response)
        return response
    except Exception as e:
        print(e)
        raise e

The code outlined above facilitates the following functionalities:

  • Each image uploaded triggers the creation of a new DynamoDB item, with the S3 Object Key serving as the primary key.
  • The item includes a list of corresponding labels retrieved from Amazon Rekognition, stored as a set of strings in DynamoDB.
  • Storing labels in DynamoDB enables repeated retrieval without additional queries to Amazon Rekognition.
  • Labels can be retrieved either by their primary key or by scanning the DynamoDB table and filtering for specific labels using a CONTAINS DynamoDB filter.

You can now test the function by uploading one or more images to the “images” folder in your bucket

Go to the DynamoDB table and click on Explore items from the left pane, you will find the items returned with the labels that are recognized by AWS Rekognitions

table1

Conclusion
In this tutorial, you set up an Amazon S3 bucket and configured an AWS Lambda function to trigger when images are uploaded to the bucket. You implemented the Lambda function to call the Amazon Rekognition API, label the images, and store the results in Amazon DynamoDB. Finally, you demonstrated how to search DynamoDB for image labels.

This serverless setup offers flexibility, allowing for customization of the Lambda function to address more complex scenarios with minimal effort.

The post Image Labeling with Amazon Rekognition appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/02/21/image-labeling-with-amazon-rekognition/feed/ 0
My Personal Serverless Rust Developer Experience. It’s Better Than You Think https://prodsens.live/2024/02/10/my-personal-serverless-rust-developer-experience-its-better-than-you-think/?utm_source=rss&utm_medium=rss&utm_campaign=my-personal-serverless-rust-developer-experience-its-better-than-you-think https://prodsens.live/2024/02/10/my-personal-serverless-rust-developer-experience-its-better-than-you-think/#respond Sat, 10 Feb 2024 21:20:25 +0000 https://prodsens.live/2024/02/10/my-personal-serverless-rust-developer-experience-its-better-than-you-think/ my-personal-serverless-rust-developer-experience.-it’s-better-than-you-think

One of the things that can be difficult when starting with a new technology, framework or tool is…

The post My Personal Serverless Rust Developer Experience. It’s Better Than You Think appeared first on ProdSens.live.

]]>
my-personal-serverless-rust-developer-experience.-it’s-better-than-you-think

One of the things that can be difficult when starting with a new technology, framework or tool is where to get started. That “get started” can mean a great many things to many people. Over the past 6 months or so, I’ve been learning and deploying Rust into production in AWS. I’ve gone back and forth on my workflow and wanted to put together a Serverless Rust Developer Experience article. As you begin with Rust and Serverless, this should give you some good places to get started.

Serverless Rust Developer Experience

Where does it Start

Let’s pretend for a moment that I receive a new feature request from my product owner. It’ll start something like this.

“We need to build a capability that when a customer clicks the ‘z’ button, we calculate the value of the input fields and return them an answer. Can we do that?”

The answer is, of course, YES, I can make this happen. To lead into this article, I’m going to reach for Lambda, Rust and Serverless.

So I want to build a Lambda that handles a user web request. What kinds of tools and patterns do I personally use to accomplish this task?

Developer Experience

The topic of developer experience is highly subjective. However, I tend to group what it’s like to perform the following activities during the delivery process.

  • Writing the code
    • Which IDE
    • Project organization
  • Build and debug process
  • Testing locally with close-to-real scenarios
  • Deploying the bundle which could be with Docker, binaries and bundled code
  • Observability falls here too but I addressed that here

Writing the Code

There are two important things that I’ve had to settle on in this area. I’m one of those developers that settles in ONCE I get comfortable. But if I’m not comfortable, I’m always looking for that nice comfortable spot.

Which IDE

I’m on the record of loving the VSCode experience with Rust. And I do think that it’s amazing that a “non-IDE” can feel so much like an IDE. However, I’ve recently pivoted off of that stance. I know it’s still in EAP, but Rust Rover gives me all of the things that I get from VSCode plus an easier integration with LLDB.

Back to the whole comfort thing. When I find a theme or a look that I like, I tend to use it everywhere. One Dark is the theme that applies across all of my Jet Brains IDEs, VSCode and iTerm.

Rust Rover Developer Experience

I’m a strong believer in knowing your tools so find what works and try and stick with it so that you become a master of its features.

Project Organization

When crafting a solid Serverless Rust Developer Experience, the layout of the project matters to me. What I’ve come to settle on is using Cargo’s workspaces to isolate my Lambda source code while also allowing for shared code in separate project crates. Cargo supports binary and library projects so this fits nicely in with that setup.

When working with Cargo, Cargo Lambda and CDK I like to break my Lambda projects like this:

  • Directory for each Lambda function
  • Directory that holds the shared code library
  • Directory for infra which is the CDK code
  • One final for test events

Project Setup

A sample Cargo.toml that accomplishes this at the root project level might look like this.

[workspace]
members = [
    "lambda-one",
    "lambda-two"
]

Build and Debug Process

Without a solid build and debug experience, achieving a quality Serverless Rust Developer Experience would be next to impossible. For the next two sections of my setup, I leverage Cargo Lambda pretty hard. Cargo Lambda is a project that brings a subcommand into the Cargo ecosystem for building and testing Lambdas locally. I could also use it for deploying, but I stick to CDK for that.

Building

To build either one or many Lambda functions, I simply issue this command in the root of the project directory.

cargo lambda build

One of the nice things about Cargo Lambda is that it supports cross-compilation. If I want to build for Graviton, I can run.

cargo lambda build --arm64

And finally, if I want to package for release.

cargo lambda build --arm64 --release

Debugging

Now what would the Serverless Rust Developer Experience be without local debugging?

I take two different approaches to debugging code locally.

Path one is to use tracing statements to emit logs so that I can view whatever it is that I want. I find this useful in most cases because I find that I don’t always use an interactive debugger unless something is going wrong.

async fn function_handler(event: LambdaEvent<SqsEvent>) -> Result<(), Error> {
    // Extract some useful information from the request
    info!("(Event)={:?}", event.payload);
    Ok(())
}

Path two is to leverage the interactive debugger. Rust has support for LLDB which integrates nicely into Rust Rover. From there, I can attach Rust Rover to the cargo lambda watch that I have running and I get interactive debugging.

Serverless Rust Developer Experience Debugging

With Lambda development I started to get used to not having solid interactive debugging but the experience is improving and has been for quite some time. I don’t always run the interactive debugger, but when I need it, I’m glad I have it.

Testing Locally

I’ve said it so many times recently, Cargo Lambda is the way to go when building Lambda with Rust. The Serverless Rust Developer Experience is greatly enhanced by this subcommand.

I use the local tooling quite a bit in the following way.

The first thing is to fire up the watcher. If you are familiar with nodemon or something similar, watching code is going to seem familiar. I honestly don’t do this with compiled languages much. I’m not sure why not, but since Cargo Lambda uses it as part of the process, I’m happy to follow along.

Starting the watcher

With the watcher running, I’m going to run a sample event through my code.

Sample event

And then side-by-side they look like this.

Side-by-side

Cargo Lambda supports using templated events, custom event files or even passing data as an ASCII string. I’ve got a lot of options for how I want to exercise my code locally with various event payloads.

Deploying the Bundle

I shared this tweet a bit ago and I am 100% settled on CDK for building and shipping Rust Lambdas. Cargo Lambda also has a nice CDK Construct that wraps some of the cross-compilation pieces as well as how to source the project files.

This is a simple example, but my TypeScript code just creates a new Function. The RustFunction construct inherits from the LambdaFunction which allows me to set things like environment variables or the architecture runtime.

new RustFunction(scope, 'LambdaOne', {
    manifestPath: './lambda-one'
})

While Cargo Lambda does have a way to deploy your stack, which I like, I find that using CDK for local to cloud deploys seamlessly. And then that same code can be used as part of a bigger CDK Pipeline if that’s what I require.

Additional Thoughts

A Serverless Rust Developer Experience can take on many shapes and is often a personal thing. However, the below will be pretty consistent throughout.

  • Writing code
  • Building code
  • Testing code
  • Deploying code

Other things to consider that I didn’t mention.

AI code assistants have become super popular lately. I’m an AWS first person, so I tend to stay in their ecosystem. I’m OK with that bias too. With that said, CodeWhisperer has been a dream for me to work with. It does well in VSCode and with Rust Rover.

The Serverless Application Model (SAM) is another approach to developing Lambdas in Rust. It also provides a solid developer experience. Be advised, that you do need to enable beta features as it uses Cargo Lambda behind the scenes as well. I tend to find too much overlap between Cargo Lambda and SAM, but if you like SAM better than CDK, you’ll be just fine.

Finally, I didn’t mention source code control. That topic is very personal to people. I don’t tend to use my IDE for managing Git. I like to use something external that gives me a “best-in-breed” solution. That tool for me is Fork. I’ve shared this tool before, but never in an article. If you are like me and enjoy something visual and easy to work with, Fork fits those requirements.

Wrapping Up

Getting started with something new can sometimes be hard, scary or even just confusing. My aim with this article was to show you how I build and ship production-grade Lambdas with Rust. The Serverless Rust Developer Experience is world-class at this point and it will only keep improving. This isn’t the only way by any means but will give you a solid starting point that you can experiment and build your patterns.

As you start new projects, if you take into account the things I’ve shared above, you’ll be in a better starting spot than I was when I got going some months back. And as you learn and get better with Rust and Lambda, I’d love to see how we can all make this even better.

As always, thanks for reading and happy building!

The post My Personal Serverless Rust Developer Experience. It’s Better Than You Think appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/02/10/my-personal-serverless-rust-developer-experience-its-better-than-you-think/feed/ 0
[20 Days of DynamoDB] Day 14 – Using the DynamoDB expression package to build Key Condition and Filter expressions https://prodsens.live/2024/01/30/20-days-of-dynamodb-day-14-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions/?utm_source=rss&utm_medium=rss&utm_campaign=20-days-of-dynamodb-day-14-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions https://prodsens.live/2024/01/30/20-days-of-dynamodb-day-14-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions/#respond Tue, 30 Jan 2024 05:20:43 +0000 https://prodsens.live/2024/01/30/20-days-of-dynamodb-day-14-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions/ [20-days-of-dynamodb]-day-14-–-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions

Posted: 30/Jan/2024 You can use expression package in the AWS Go SDK for DynamoDB to programmatically build key…

The post [20 Days of DynamoDB] Day 14 – Using the DynamoDB expression package to build Key Condition and Filter expressions appeared first on ProdSens.live.

]]>
[20-days-of-dynamodb]-day-14-–-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions

Posted: 30/Jan/2024

You can use expression package in the AWS Go SDK for DynamoDB to programmatically build key condition and filter expressions and use them with Query API.

Here is an example that queries for a specific thread based on the forum name (partition key) and subject (sort key):

    keyConditionBuilder := expression.Key("ForumName").Equal(expression.Value("Amazon DynamoDB"))
    filterExpressionBuilder := expression.Name("Views").GreaterThanEqual(expression.Value(3))

    expr, _ := expression.NewBuilder().
        WithKeyCondition(keyConditionBuilder).
        WithFilter(filterExpressionBuilder).
        Build()

    _, err := client.Query(context.Background(), &dynamodb.QueryInput{
        TableName:                 aws.String("Thread"),
        KeyConditionExpression:    expr.KeyCondition(),
        FilterExpression:          expr.Filter(),
        ExpressionAttributeNames:  expr.Names(),
        ExpressionAttributeValues: expr.Values(),
    })

Recommended reading – Key and NameBuilder in the package API docs

The post [20 Days of DynamoDB] Day 14 – Using the DynamoDB expression package to build Key Condition and Filter expressions appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/01/30/20-days-of-dynamodb-day-14-using-the-dynamodb-expression-package-to-build-key-condition-and-filter-expressions/feed/ 0
[20 Days of DynamoDB] Day 6 – Atomic counters with UpdateItem https://prodsens.live/2024/01/15/20-days-of-dynamodb-day-6-atomic-counters-with-updateitem/?utm_source=rss&utm_medium=rss&utm_campaign=20-days-of-dynamodb-day-6-atomic-counters-with-updateitem https://prodsens.live/2024/01/15/20-days-of-dynamodb-day-6-atomic-counters-with-updateitem/#respond Mon, 15 Jan 2024 03:24:48 +0000 https://prodsens.live/2024/01/15/20-days-of-dynamodb-day-6-atomic-counters-with-updateitem/ [20-days-of-dynamodb]-day-6-–-atomic-counters-with-updateitem

Need to implement atomic counter using DynamoDB? If you have a use-case that can tolerate over-counting or under-counting…

The post [20 Days of DynamoDB] Day 6 – Atomic counters with UpdateItem appeared first on ProdSens.live.

]]>
[20-days-of-dynamodb]-day-6-–-atomic-counters-with-updateitem

Need to implement atomic counter using DynamoDB? If you have a use-case that can tolerate over-counting or under-counting (for example, visitor count), use the UpdateItem API.

Here is an example that uses the SET operator in an update expression to increment num_logins attribute:

    resp, err := client.UpdateItem(context.Background(), &dynamodb.UpdateItemInput{
        TableName: aws.String(tableName),
        Key: map[string]types.AttributeValue{
            "email": &types.AttributeValueMemberS{Value: email},
        },
        UpdateExpression: aws.String("SET num_logins = num_logins + :num"),
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":num": &types.AttributeValueMemberN{
                Value: num,
            },
        },
        ReturnConsumedCapacity: types.ReturnConsumedCapacityTotal,
    })

Note that every invocation of UpdateItem will increment (or decrement) – hence it is not idempotent.

Recommended reading – Atomic Counters

The post [20 Days of DynamoDB] Day 6 – Atomic counters with UpdateItem appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/01/15/20-days-of-dynamodb-day-6-atomic-counters-with-updateitem/feed/ 0
DaisyUI + Alpine.js + Codehooks.io – the simple web app trio https://prodsens.live/2024/01/13/daisyui-alpine-js-codehooks-io-the-simple-web-app-trio/?utm_source=rss&utm_medium=rss&utm_campaign=daisyui-alpine-js-codehooks-io-the-simple-web-app-trio https://prodsens.live/2024/01/13/daisyui-alpine-js-codehooks-io-the-simple-web-app-trio/#respond Sat, 13 Jan 2024 17:25:21 +0000 https://prodsens.live/2024/01/13/daisyui-alpine-js-codehooks-io-the-simple-web-app-trio/ daisyui-+-alpinejs-+-codehooks.io-–-the-simple-web-app-trio

Hello and welcome to this hands-on tutorial where we’ll create a simple and interactive web app using DaisyUi,…

The post DaisyUI + Alpine.js + Codehooks.io – the simple web app trio appeared first on ProdSens.live.

]]>
daisyui-+-alpinejs-+-codehooks.io-–-the-simple-web-app-trio

Hello and welcome to this hands-on tutorial where we’ll create a simple and interactive web app using DaisyUi, Alpine.js and Codehooks.io.

This guide is tailored for front-end developers looking to explore the smooth integration of DaisyUI’s stylish components, Alpine.js’s minimalist reactive framework, and the straightforward back-end capabilities of Codehooks.io.

Whether you’re an experienced developer or just starting out, this tutorial will walk you through the essential steps of web app development and deployment.

The finished result of this Web App is shown in the screen shot below.

Buzzword generator screenshot
Check out a live example here

What you will learn from this tutorial

DaisyUI

DaisyUI is a component library for Tailwind CSS, offering a range of pre-styled components for rapid UI development. It’s perfect for developers who want to create elegant interfaces quickly without sacrificing the customizability that Tailwind provides.

Alpine.js

Alpine.js is a minimalist JavaScript framework for adding interactive elements to your HTML. It’s ideal for developers who need a lightweight, straightforward tool to enhance their website’s UI with dynamic features like dropdowns and modals.

Codehooks.io

Codehooks.io is a serverless platform that simplifies backend development. It allows you to easily deploy serverless functions and APIs, focusing on your code while it handles the infrastructure, scaling, and maintenance.

OK, let’s learn how to build the Web App.

Project Setup

We need to create a project directory for the source code files and initialize npm.

mkdir myproject && cd myproject
mkdir webapp
npm init -y
npm install codehooks-js

Later we’ll edit the package.json and add the deployment command for codehooks.io.

Now, create the Web App source files. In the root directory create the two files index.jsand buzzwords.js for the server side code.

In the ‘webapp’ directory, create the two files for the client side code index.html and script.js. The touch command is a handy helper.

touch index.js buzzwords.js webapp/index.html webapp/main.js

Codehooks.io Account Setup

Next you’ll need to connect the local project directory to your Codehooks.io project space.

Sign up for an account at Codehooks.io, and create a new project, e.g. mywebapp.

Install the Codehooks.io CLI.

npm install -g codehooks

Login to your account.

coho login

Connect your local web app project with your account/project.

coho init --empty

Use the up/down keys to pick your project and database environment (default dev) from the list. The example project list from my personal account is shown below, where i pick mywebapp-okuc as the active project to host my Web App.

cohodev init
? Select project 
  customers-chsb 
  myapi-4hvc 
❯ mywebapp-okuc 
  mongomirror-g4g9

Select your particular project and press enter, this will create a config.json file in your project directory with the unique project name.

Finally, your project directory should look like this after running the above commands succesfully.

.
├── buzzwords.js
├── config-dev.json
├── index.js
├── package-lock.json
├── package.json
└── webapp
    ├── index.html
    └── main.js

This concludes the (boring) tasks we need to setup our project, let’s move on with the fun stuff of developing the frontend and the backend.

Developing the Frontend (index.html)

The web app frontend is a really simple HTML page that uses DaisyUI for the looks and alpine.js for the reactive dynamics.

In the we include CDN links for Tailwind CSS, DaisyUI, and Alpine.js.


 lang="en">


   name="viewport" content="width=device-width, initial-scale=1.0">
  
   href="https://cdn.jsdelivr.net/npm/tailwindcss@^2.2.19/dist/tailwind.min.css" rel="stylesheet">
   href="https://cdn.jsdelivr.net/npm/daisyui@^1.3.6/dist/full.css" rel="stylesheet">
  
  
  
   href="https://dev.to/dev/" />



   class="container mx-auto p-8">
    

class="text-4xl font-bold text-center">Buzzword generator!

x-data="messageStore" class="flex justify-center"> class="card w-96 bg-base-100 shadow-xl"> class="card-body">

class="card-title">Buzz count class="badge badge-lg" x-text="bsCount">

class="toast toast-top toast-center"> class="alert alert-info"> x-text="message"> class="card-actions justify-end"> @click="getMessage()" class="btn btn-primary">Get Buzzwords

Backend API Integration (main.js)

We’ll use the alpine.js powerful x-data directive to handle dynamic content and reactive binding. The client calls the Codehooks.io server REST API /api/message, and the REST API returns a dynamic JSON object as a response to the client request.

The client side code webapp/main.js is shown below.


document.addEventListener('alpine:init', () => {
  Alpine.data('messageStore', () => ({
    message: 'Click the button to fetch a message ...',
    bsCount: 0,
    // init is called when document is ready
    init() {      
      this.getMessage()
    },
    // fetch json from the server public api
    async getMessage() {
      try {
        const response = await fetch('api/message', {
          headers: {
            'Content-Type': 'application/json'
          }
        });
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        // destruct json from server
        const { message, bsCount } = await response.json();
        // update local Alpine.data model
        this.message = message;
        this.bsCount = bsCount;
      } catch (error) {
        console.error('Error:', error);
      }
    }
  }));
});

The next section shows how to create the server side for the REST API.

Backend Server Setup (index.js)

The server code is a simple Codehooks.io backend that exposes one single public route GET /api/message and serve the webapp files from the webapp sub directory. The random “buzzwords” is generated by combining 3 arrays of random words imported from the buzzword.js file.

Note that we also use the build in Key Value datastore to increment the total count of generated buzzwords.

The complete server code is shown below.


/*
* Codehooks.io backend example app
*/
import {app, datastore} from 'codehooks-js'
import { buzzwords1, buzzwords2, buzzwords3 } from './buzzwords';


function generateBS() {
  const word1 = buzzwords1[Math.floor(Math.random() * buzzwords1.length)];
  const word2 = buzzwords2[Math.floor(Math.random() * buzzwords2.length)];
  const word3 = buzzwords3[Math.floor(Math.random() * buzzwords3.length)];

  return `${word1} ${word2} ${word3}`;
}

// allow api access without api token
app.auth('/api/message*', (req, res, next) => {
  if (req.method === 'GET') {
    next();
  } else {
    next('Only GET is allow public access');
  }
})

// api route for generating a buzzword message and a count
app.get('/api/message', async (req, res) => {
  const db = await datastore.open();
  const bsCount = await db.incr('bsCount', 1)
  console.log('Count', bsCount)
  res.json({bsCount, message: generateBS()})
})

// serve the webapp directory on the / route
app.static({route: "/", directory: "/webapp"})

// bind to serverless runtime
export default app.init();

In this simple example we combine the arrays of words from the buzzwords.js file shown below.

export const buzzwords1 = [
    "Blockchain", "Quantum", "Synergistic", "Big Data", ...
];

export const buzzwords2 = [
    "driven", "enhanced", "optimized", "integrated", ...
];

export const buzzwords3 = [
    "solutions", "platforms", "paradigms", "algorithms", ...
];

Deployment Using Codehooks CLI

We’ll use the Codehooks CLI for deployment. And to automate things we create script deploy commands in our package.json as shown below.

{
  "name": "mywebapp",
  "version": "1.0.0",
  "description": "My awesome webapp",
  "main": "index.js",
  "scripts": {
    "predeploy": "codehooks upload ./webapp",
    "deploy": "codehooks deploy"
  },
  "dependencies": {
    "codehooks-js": "^1.1.10"
  }
}

The ‘deploy’ command will upload all files in the webapp directory to the server and then deploy your backend server (index.js) to the serverless cloud.

To deploy the complete web app and the backend API run:

npm run deploy

Testing and Domain Configuration

You can access your deployed app and verify its functionality in several ways.

  • Run the coho info command to find your project URL, in my example project the URL is: https://bsgenerator-kge3.api.codehooks.io/dev, copy this into your browser and test the Web App
  • You can also add a custom domain (e.g. example.com) that you control by adding an A-Record or C-Record and point it to Codehooks. This generates a free SSL certificate with auto renew. (Check how to setup this in your admin settings in the Codehooks Studio App)
  • Test the server REST API with Postman or cUrl by calling the canonical URL for the API, e.g. curl --location 'curl --location 'https://bsgenerator-kge3.api.codehooks.io/dev/api/message''
  • Coming soon! Use the built in random domain for development, in my example app the public domain is: mighty-frog-f100.codehooks.io.

Concluding Thoughts

The tutorial aims to demonstrate the simplicity and efficiency of combining DaisyUI, Alpine.js, and Codehooks.io in web development, guiding through each stage from setup to deployment. The final product is a dynamic web application capable of generating random messages, showcasing the power of these tools in creating responsive and interactive web applications.

The post DaisyUI + Alpine.js + Codehooks.io – the simple web app trio appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/01/13/daisyui-alpine-js-codehooks-io-the-simple-web-app-trio/feed/ 0
Integration Testing Vercel Serverless Functions with OpenTelemetry https://prodsens.live/2024/01/09/integration-testing-vercel-serverless-functions-with-opentelemetry/?utm_source=rss&utm_medium=rss&utm_campaign=integration-testing-vercel-serverless-functions-with-opentelemetry https://prodsens.live/2024/01/09/integration-testing-vercel-serverless-functions-with-opentelemetry/#respond Tue, 09 Jan 2024 15:24:13 +0000 https://prodsens.live/2024/01/09/integration-testing-vercel-serverless-functions-with-opentelemetry/ integration-testing-vercel-serverless-functions-with-opentelemetry

Today you’ll learn how to create production-ready serverless functions using Vercel. It will include adding distributed tracing with…

The post Integration Testing Vercel Serverless Functions with OpenTelemetry appeared first on ProdSens.live.

]]>
integration-testing-vercel-serverless-functions-with-opentelemetry

Today you’ll learn how to create production-ready serverless functions using Vercel. It will include adding distributed tracing with OpenTelemetry for troubleshooting and integration testing with Tracetest for trace-based testing.

Once you’re done reading you’ll have a complete boilerplate project to use for your own Vercel serverless functions interacting with Vercel Postgres as storage. More importantly I’ll explain how to locally test your serverless functions in a typical development lifecycle and create integration tests for CI pipelines packaged in Docker.

If you can’t wait until the end, clone the example from GitHub, get your API keys and tokens after signing up at [app.tracetest.io](https://app.tracetest.io), and create a Vercel Postgres instance. Add the Vercel Postgres env vars to your .env files and spin up the tests with Docker. Make sure to have Docker and Docker Compose installed for the quick start!

git clone git@github.com:kubeshop/tracetest.git
cd tracetest/examples/integration-testing-vercel-functions
docker compose up -d --build
docker compose run integration-tests

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704656325/Blogposts/integration-testing-vercel-functions/ezgif.com-animated-gif-maker_3_tpetvk.gif

Why Serverless Functions?

Serverless functions are server-side code run on cloud servers without the need to be deployed to a server. They are instead executed in cloud environments and eliminate traditional infrastructure needs.

Platforms like Vercel that host serverless functions and front-end code offer developers scalability and flexibility with no infrastructure overhead.

Observability for Serverless Architecture

Serverless Architecture often struggles with visibility. However, observability tools can help. They trace events from start to finish, collect metrics, and evaluate how systems manage these events.

OpenTelemetry, an open-source observability framework, is one such tool. It helps gather, process, and export data like traces, metrics, and logs. Traces are especially useful as they provide insights into how distributed systems perform by tracing requests across various services.

Luckily for us, Vercel has built-in support for OpenTelemetry! 🔥

Testing Serverless Architecture

How does this tie into testing? Running tests against serverless can be quite tiresome since you can only run black box tests.

But, what if you can use distributed tracing for testing as well? Now you can. Using OpenTelemetry and Tracetest in your development lifecycle enables both local testing and integration testing in your CI pipelines. Let’s jump in and you’ll learn exactly how to do it yourself!

Vercel Function Architecture

Today you’ll build a Vercel function that imports a Pokemon into a Pokedex!

The function will fetch data from an external API, transform the data and insert it into a Vercel Postgres database. This particular flow has two failure points that are difficult to test.

  1. Validating that an external API request from a Vercel function is successful.
  2. Validating that a Postgres insert request is successful.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704655783/Blogposts/integration-testing-vercel-functions/vercel-func-arch_lnvjj0.png

Install Vercel and Create a Boilerplate

Deployment and development is managed with the Vercel Command Line Interface (CLI), and I’ll be using Next.js to simplify the setup and development of serverless functions.

The initial step involves creating a Vercel account, installing the CLI, and authenticating through the CLI.

npm i -g vercel@latest
vercel login

Next create the Next.js boilerplate.

vercel init nextjs

Rename the nextjs directory to integration-testing-vercel-functions. Now you’re ready to deploy it to Vercel.

vercel deploy

This will create a project in the Vercel Dashboard.

Create a Vercel Postgres Database

Go to the Storage tab in your Vercel account and create a Postgres database. Give it a name. I’ll use pokedex since I’ll show how to catch some Pokemon!

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704701080/Blogposts/integration-testing-vercel-functions/Screenshot_2024-01-07_at_10.28.42_usdwmn.png

Use psql to connect to the database.

psql "postgres://default:************@ep-morning-wood-76425109.us-east-1.postgres.vercel-storage.com:5432/verceldb

And, create a table.

CREATE TABLE IF NOT EXISTS pokemon (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

Proceed to connect the database to your project and pull the environment variables to your local development.

vercel link
vercel env pull .env.development.local

This will create a .env.development.local file in your local directory that contains all the Postgres connection details.

# Vercel Postgres
POSTGRES_DATABASE="**********"
POSTGRES_HOST="**********"
POSTGRES_PASSWORD="**********"
POSTGRES_PRISMA_URL="**********"
POSTGRES_URL="**********"
POSTGRES_URL_NON_POOLING="**********"
POSTGRES_USER="**********"

# ...

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704701081/Blogposts/integration-testing-vercel-functions/Screenshot_2024-01-07_at_10.39.35_r2ze6w.png

Finally install the Vercel Postgres SDK to interact with the database via code

npm install @vercel/postgres

You’re ready to start building!

Create a Serverless Function

You’ll create an import flow. Send the ID of a Pokemon to the serverless function, it handles getting the Pokemon info from an external API and stores it in the pokedex database.

In the root directory create a /pages directory with an /api directory inside of it, and create pokemon.ts there.

Your structure should look like this.

/pages
  /api
    /pokemon.ts

Every file within the /api directory maps to a specific API route. The function you’ll create will be accessible at the URL [http://localhost:3000/api/pokemon](http://localhost:3000/api/pokemon).

Each function takes a request as input and is expected to return a response. Failing to return a response will result in a timeout.

To return JSON data, you’ll use the res.status(200).json({...}) method. Async/Await flows are enabled by default as well!

Here’s an example of a POST request with a GET request to an external API from within the serverless function and then inserting the data into Postgres. It’s a common point-of-failure that is hard to troubleshoot and test.

import type { NextApiRequest, NextApiResponse } from 'next'
import { sql } from '@vercel/postgres'

export async function addPokemon(pokemon: any) {
  return await sql`
    INSERT INTO pokemon (name)
    VALUES (${pokemon.name})
    RETURNING *;
  `
}

export async function getPokemon(pokemon: any) {
  return await sql`
    SELECT * FROM pokemon where id=${pokemon.id};
  `
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const requestUrl = `https://pokeapi.co/api/v2/pokemon/${req.body.id || '6'}`
    const response = await fetch(requestUrl)
    const resPokemon = await response.json()

    const { rowCount, rows: [addedPokemon, ...addedPokemonRest] } = await addPokemon(resPokemon) 
    res.status(200).json(addedPokemon)

  } catch (err) {
    res.status(500).json({ error: 'failed to load data' })
  }
}

Go ahead and run the function.

npm run dev

Navigate to http://localhost:3000/api/pokemon to see the response from your serverless function.

{"id":13,"name":"charizard","createdAt":"2024-01-07T13:56:12.379Z"}

Configure Troubleshooting with OpenTelemetry and Distributed Tracing

OpenTelemetry libraries for Node.js are stable and include auto-instrumentation. I’d like to say it’s automagical since it gives you distributed tracing out of the box by just adding a few modules and a single preloaded JavaScript file.

Note: To learn more check out the official Vercel docs about OpenTelemetry or take a look at the official OpenTelemetry docs.

Firstly you need to install OpenTelemetry packages:

npm install 
    @opentelemetry/sdk-node 
    @opentelemetry/resources 
    @opentelemetry/semantic-conventions 
    @opentelemetry/sdk-trace-node 
    @opentelemetry/exporter-trace-otlp-http 
    @opentelemetry/exporter-trace-otlp-grpc 
    @opentelemetry/api 
    @opentelemetry/auto-instrumentations-node 
    @opentelemetry/instrumentation-fetch

Note: OpenTelemetry APIs are not compatible with the edge runtime, so you need to make sure that you are importing them only when process.env.NEXT_RUNTIME === 'nodejs'. The official Vercel docs recommend creating a new file instrumentation.node.ts to import only when using Node.

Start by enabling OpenTelemetry instrumentation in the Next.js app by setting the experimental.instrumentationHook: true in the next.config.js like below.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
    // ...
}

module.exports = nextConfig

Then, create a file called instrumentation.ts to initialize the NodeSDK for OpenTelemetry in your serverless functions.

// instrumentation.ts

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node')
  }
}

Since you need a custom configuration and a dedicated file for the Node.js runtime, create another file called instrumentation.node.ts and paste this code into it.

import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'

const sdk = new NodeSDK({
  // The OTEL_EXPORTER_OTLP_ENDPOINT env var is passed into "new OTLPTraceExporter" automatically.
  // If the OTEL_EXPORTER_OTLP_ENDPOINT env var is not set the "new OTLPTraceExporter" will
  // default to use "http://localhost:4317" for gRPC and "http://localhost:4318" for HTTP.
  // This sample is using HTTP.
  traceExporter: new OTLPTraceExporter(),
  instrumentations: [
    getNodeAutoInstrumentations(),
    new FetchInstrumentation(),
  ],
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'integration-testing-vercel-functions',
  }),
})
sdk.start()

This will configure the OpenTelemetry libraries and trace exporters. The OTLPTraceExporter will look for an environment variable called OTEL_EXPORTER_OTLP_ENDPOINT. If not found it’ll default to using [http://localhost:4318](http://localhost:4318) for HTTP traffic. Add it to your .env.development.local file.

# OTEL
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

Now, you need to add a tracer to the Vercel function. Replace the code in your pages/api/pokemon.ts with this.

import { trace, SpanStatusCode } from '@opentelemetry/api'
import type { NextApiRequest, NextApiResponse } from 'next'
import { sql } from '@vercel/postgres'

export async function addPokemon(pokemon: any) {
  return await sql`
    INSERT INTO pokemon (name)
    VALUES (${pokemon.name})
    RETURNING *;
  `
}

export async function getPokemon(pokemon: any) {
  return await sql`
    SELECT * FROM pokemon where id=${pokemon.id};
  `
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const activeSpan = trace.getActiveSpan()
  const tracer = await trace.getTracer('integration-testing-vercel-functions')

  try {

    const externalPokemon = await tracer.startActiveSpan('GET Pokemon from pokeapi.co', async (externalPokemonSpan) => {
      const requestUrl = `https://pokeapi.co/api/v2/pokemon/${req.body.id || '6'}`
      const response = await fetch(requestUrl)
      const { id, name } = await response.json()

      externalPokemonSpan.setStatus({ code: SpanStatusCode.OK, message: String("Pokemon fetched successfully!") })
      externalPokemonSpan.setAttribute('pokemon.name', name)
      externalPokemonSpan.setAttribute('pokemon.id', id)
      externalPokemonSpan.end()

      return { id, name }
    })

    const addedPokemon = await tracer.startActiveSpan('Add Pokemon to Vercel Postgres', async (addedPokemonSpan) => {
      const { rowCount, rows: [addedPokemon, ...rest] } = await addPokemon(externalPokemon)
      addedPokemonSpan.setAttribute('pokemon.isAdded', rowCount === 1)
      addedPokemonSpan.setAttribute('pokemon.added.name', addedPokemon.name)
      addedPokemonSpan.end()
      return addedPokemon
    })

    res.status(200).json(addedPokemon)

  } catch (err) {
    activeSpan?.setAttribute('error', String(err))
    activeSpan?.recordException(String(err))
    activeSpan?.setStatus({ code: SpanStatusCode.ERROR, message: String(err) })
    res.status(500).json({ error: 'failed to load data' })
  } finally {
    activeSpan?.end()
  }
}

What is going on here?

  1. You first import the @vercel/postgres SDK.
  2. Then, define two functions for interacting with Vercel Postgres, addPokemon and getPokemon.
  3. The code instantiates a tracer with trace.getTracer.
  4. You get the activeSpan and instantiate a tracer with trace.getTracer. The active span in this context is the span from the Vercel function itself.
  5. It uses the tracer.startActiveSpan to wrap the external HTTP request and a Vercel Postgres database call. This will generate a trace span that will attach any child spans to it. What I want you to care about is making sure the fetch request’s trace spans are attached the correct parent span. This happens automatically with the tracer.startActiveSpan.
  6. It uses span.setStatus and span.setAttribute to attach values to the span itself.
  7. It uses span.recordException(String(err)) in the exception handler to make sure to record an exception if it happens.
  8. Finally, pun intended, it ends the span with the span.end method.

With this, your Next.js app will emit distributed traces! But, you’re not sending them anywhere. Let’s fix that by adding Tracetest to the development lifecycle.

The Modern Way of Testing Serverless Functions Locally

Spin up your Next.js app.

npm run dev

This starts the server and function on http://localhost:3000/api/pokemon.

Tracetest is a trace-based testing tool for building integration tests in minutes using OpenTelemetry traces. Create test specs against trace data at every point of a request transaction. It’s open source and has a managed cloud offering as well.

Since it’s easier to get started with no dependencies, I’ll show how to use Tracetest instead of Tracetest Core. But, you can still get the same functionality with Tracetest Core!

To get started with Tracetest:

  1. You’ll need to download the CLI for your operating system. Sample for MacOS below.
brew install kubeshop/tracetest/tracetest
  1. And, sign up for an account. Go ahead and do that now.

The CLI is bundled with Tracetest Agent that runs in your infrastructure to collect responses and traces for tests. Learn more in the docs here.

To start Tracetest Agent add the --api-key flag manually at startup or, just run tracetest start and pick your env from the menu.

You can find the organization and environment values in the Settings > Agent in the Tracetest app.

Now, go ahead and start Tracetest Agent.

tracetest start --api-key 

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704636693/Blogposts/integration-testing-vercel-functions/screely-1704636677545_ovv44w.png

With the Tracetest Agent started, go back to [app.tracetest.io](http://app.tracetest.io) and trigger the serverless function.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704636858/Blogposts/integration-testing-vercel-functions/screely-1704636842453_dgjt3k.png

Switch to the Trace tab to see the full preview of the distributed trace.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704654844/Blogposts/integration-testing-vercel-functions/screely-1704654837147_e5jsp8.png

From here you can add test specs to validate that the HTTP request never fails. Including both the downstream API and both Vercel Postgres database connections.

Click the Test tab and add a test spec from the snippets. Select All HTTP Spans: Status code is 200.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704654903/Blogposts/integration-testing-vercel-functions/screely-1704654897438_uq1p9a.png

Save the test spec. You now see the test is passing since both the external HTTP requests and database interactions are passing and returning status code 200.

All this enabled by OpenTelemetry tracing and Tracetest! What’s also awesome is that these tests are stored in your Tracetest Account and you can revisit them and run the same tests again every time you run your dev environment!

This is awesome for your development lifecycle and API testing while developing Vercel functions, but also in pre-merge testing, and integration testing.

Let me explain how to enable integration testing in CI pipelines next!

Integration Testing Vercel Serverless Functions

Finally, check out the Automate tab.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1704637418/Blogposts/integration-testing-vercel-functions/screely-1704637411996_rp27vc.png

Every test you create can be expressed with YAML. I know you love YAML, quit complaining! 😄

With this test definition you can trigger the same test via the CLI either locally or in any CI pipeline of you choice.

To try it locally, create a directory called test in the root directory.

Paste this into a file called api.pokemon.spec.development.yaml.

# api.pokemon.spec.development.yaml

type: Test
spec:
  id: kv8C-hOSR
  name: Test API
  trigger:
    type: http
    httpRequest:
      method: POST
      url: http://localhost:3000/api/pokemon
      body: "{n  "id": "6"n}"
      headers:
      - key: Content-Type
        value: application/json
  specs:
  - selector: span[tracetest.span.type="http"]
    name: "All HTTP Spans: Status  code is 200"
    assertions:
    - attr:http.status_code = 200

Since you already have the Tracetest CLI installed, running it is as simple as one command.

tracetest run test -f ./test/api.pokemon.spec.development.yaml --required-gates test-specs --output pretty

[Output]
✔ Test API (https://app.tracetest.io/organizations/ttorg_e66318ba6544b856/environments/ttenv_0e807879e2e38d28/test/-gjd4idIR/run/22/test) - trace id: f2250362ff2f70f8f5be7b2fba74e4b2
    ✔ All HTTP Spans: Status code is 200

What’s cool is you can follow the link and open the particular test in Tracetest and view it once it’s saved in the cloud.

Let’s introduce Docker into the mix and set up a proper CI environment.

Start by creating a .env.docker file.

# OTLP HTTP
OTEL_EXPORTER_OTLP_ENDPOINT="http://tracetest-agent:4318"

# Vercel Postgres
POSTGRES_DATABASE="**********"
POSTGRES_HOST="**********"
POSTGRES_PASSWORD="**********"
POSTGRES_PRISMA_URL="**********"
POSTGRES_URL="**********"
POSTGRES_URL_NON_POOLING="**********"
POSTGRES_USER="**********"

This will make sure we set up the trace exporter endpoint to send traces to Tracetest Agent.

Then, create a docker-compose.yaml in the root directory.

version: "3"

services:
  next-app:
    image: foobar/next-app:v1
    build:
      context: .
      dockerfile: ./Dockerfile
    env_file:
      - .env.docker
    restart: always
    ports:
      - 3000:3000
    networks:
      - tracetest

  tracetest-agent:
    image: kubeshop/tracetest-agent:latest
    environment:
      - TRACETEST_API_KEY=ttagent_ # Find the Agent API Key here: https://docs.tracetest.io/configuration/agent
    ports:
      - 4317:4317
      - 4318:4318
    networks:
      - tracetest

  integration-tests:
    image: foobar/integration-tests:v1
    profiles:
      - tests
    build:
      context: ./
      dockerfile: ./test/Dockerfile
    volumes:
      - ./test/:/app/test/
    depends_on:
      tracetest-agent:
        condition: service_started
    networks:
      - tracetest

networks:
  tracetest:

Next, create a Dockerfile for the Next.js app.

FROM node:20-alpine AS base
FROM base AS builder
WORKDIR /app
RUN apk add --no-cache g++ make py3-pip

COPY package.json package-lock.json ./
RUN npm i

COPY app ./app
COPY pages ./pages
COPY public ./public
COPY next.config.js .
COPY tsconfig.json .
COPY instrumentation.ts .
COPY instrumentation.node.ts .

RUN npm run build

# Step 2. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
CMD ["node", "server.js"]

Don’t forget to set the next.config.js to include output: standalone.

/** @type {import('next').NextConfig} */
const nextConfig = {
  // ...
  output: 'standalone',
}

module.exports = nextConfig

Go back to the test directory and create a Dockerfile in it. This Dockerfile is for a container running the integration tests. It will include an installation of the Tracetest CLI and contain a bash script called run.bash to run tests.

FROM alpine
WORKDIR /app
RUN apk --update add bash jq curl
RUN curl -L https://raw.githubusercontent.com/kubeshop/tracetest/main/install-cli.sh | bash
WORKDIR /app/test/
ENTRYPOINT ["https://dev.to/bin/bash", "https://dev.to/app/test/run.bash"]

Next, create the run.bash.

#/bin/bash

# Add a Tracetest token here
# https://docs.tracetest.io/concepts/environment-tokens
tracetest configure -t tttoken_
tracetest run test -f ./api.pokemon.spec.docker.yaml --required-gates test-specs --output pretty
# Add more tests here! :D

Finally, create a api.pokemon.spec.docker.yaml file in the test directory.

# api.pokemon.spec.docker.yaml

type: Test
spec:
  id: p00W82OIR
  name: Test API
  trigger:
    type: http
    httpRequest:
      method: GET
      url: http://next-app:3000/api/pokemon # Note: Using Docker networking!
      body: "{n  "id": "6"n}"
      headers:
      - key: Content-Type
        value: application/json
  specs:
  - selector: span[tracetest.span.type="http"]
    name: "All HTTP Spans: Status code is 200"
    assertions:
    - attr:http.status_code = 200

Now, go ahead and give it a run!

docker compose up -d --build

And, trigger the integration tests.

docker compose run integration-tests

[Ouput]
[+] Creating 1/0
 ✔ Container integration-testing-vercel-functions-tracetest-agent-1  Running                                                                                             0.0s
 SUCCESS  Successfully configured Tracetest CLI
✔ Test API (https://app.tracetest.io/organizations/ttorg_e66318ba6544b856/environments/ttenv_82af376d61da80a0/test/p00W82OIR/run/8/test) - trace id: d64ab3a6f52a98141d26679fff3373b6
    ✔ All HTTP Spans: Status code is 200

Running this particular Docker Compose stack in any CI pipeline of choice is a breeze. The only dependencies you need are Docker and Docker Compose. It’s self-contained and standalone. Easily transferrable and runnable in your pipeline of choice.

Beyond Integration Testing

In conclusion, today you learned a step-by-step approach to developing, troubleshooting, and testing serverless functions using Vercel and Next.js. You started from a boilerplate Next.js project, created a serverless function, and integrated OpenTelemetry for distributed tracing.

Then you added Tracetest for trace-based testing, in both your development workflow and CI pipelines. With these tools at your disposal you can build, test, and optimize serverless functions efficiently for developing robust serverless applications.

If you get stuck along the tutorial, feel free to check out the example app in the GitHub repo, here.

Stay tuned for the next 2 parts of this series coming soon:

  • Part 2: I’ll dive into end-to-end testing by integrating Cypress with Tracetest.
  • Part 3: You’ll learn how to configure production troubleshooting and testing by using observability tools on the Vercel Marketplace.

Would you like to learn more about Tracetest and what it brings to the table? Visit the Tracetest docs and try it out by downloading it today!

Also, please feel free to join our Slack community, give Tracetest a star on GitHub, or schedule a time to chat 1:1.

The post Integration Testing Vercel Serverless Functions with OpenTelemetry appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/01/09/integration-testing-vercel-serverless-functions-with-opentelemetry/feed/ 0