Using dependencies in your functions
Overview
Functional Web Apps have both project-level dependencies and individual function-level dependencies.
Project-level dependencies are defined at the root of the project, and may include things like developer dependencies.
Function-level dependencies are isolated to each deployed cloud function.
To optimize for startup, invocation, and deployment performance, and to minimize bug surface area, Architect projects encourage single responsibility functions with the minimum number of dependencies. This intentional isolation leads to easier debugging, faster responses, and least privilege security.
Node.js
Automated dependency treeshaking
Most Architect developers using Node.js (and TypeScript) use Architect’s built-in dependency treeshaking. Here’s (roughly) it works:
- Install your dependencies in your root
package.json
(e.g.npm i @architect/functions
) import
orrequire
your dependencies as normal- When working locally, Sandbox’s Node.js process will resolve your dependencies from your project’s root
node_modules/
dir - During deployment, Architect statically analyzes your code and resolves your (non-dynamic)
import
andrequire
statements - Dependencies are installed to and deployed with your Lambda
- For debugging purposes, the automatically generated
package.json
can be found in your Lambda’snode_modules/
dir (until the next deployment or Sandbox run)- Example path:
src/http/get-index/node_modules/_arc-autoinstall/package.json
- Example path:
Using Architect’s Lambda treeshaking, generally most developers do not need to spend any time managing individual dependencies across their project’s many functions.
Tip: Lambda treeshaking also supports dependencies found in
shared
andviews
Automatically install @architect/functions
:
// src/http/get-index/index.js
import arc from '@architect/functions'
export const handler = arc.http(async req => {
return { ok: true }
})
Automatically install an optional dependency to ensure it’s available to the Lambda:
// src/http/get-index/index.js
// Note: you don't have to actually use the dep, merely importing or requiring is sufficient
require('an-optional-dependency')
export const handler = async req => {
return { ok: true }
}
Manual dependency management
Sometimes you may need fine-grained control over an individual function’s dependencies. In these cases, Architect enables per-Lambda dependency management by way of a package.json
file in your Lambda’s folder.
If a package.json
file with a dependencies
property is found in your Lambda, Architect’s treeshaking functionality will be automatically disabled for that specific Lambda, and your package.json
file will be in complete control of that Lambda’s dependencies.
Assuming the following src/http/get-index/package.json
:
{
"dependencies": {
"glob": "latest"
}
}
This Lambda would work locally in Sandbox (due to Node’s module resolution algorithm on your local machine), but would fail once deployed:
// src/http/get-index/index.js
import arc from '@architect/functions'
import glob from 'glob'
export const handler = arc.http(async req => {
return glob('**/**')
})
Relative modules & code sharing
Individual Lambdas can import
/ require
code from within their own path, but it is important to understand they cannot make use of local files that descend into other Lambdas. Attempting to do so will not work once deployed.
To share code across multiple Lambdas, please make use of @shared
and @views
, and refer to our guide on code sharing.
For example, assume the following src/http/get-index/index.js
handler:
// This is ok if it exists in the root package.json
import arc from '@architect/functions'
// This will fail
import foo from '../foo.js'
// This will work (if present, of course)
import foo from './foo.js'
// This is also ok (if foo exists in @shared)
import foo from '@architect/shared/foo.js'
Python
Automated dependency treeshaking
As with Node.js, we suggest Architect developers using Python utilize Architect’s built-in dependency treeshaking. Here’s (roughly) it works in Python:
- In addition to Architect, you will need to have
pipdeptree
installed to your system (pip3 install pipdeptree
) - Install your dependencies in your root
requirements.txt
(e.g.pip3 install architect-functions -r requirements.txt
) import
your dependencies as normal- When working locally, Sandbox will find your dependencies from your system $PATH
- During deployment, Architect statically analyzes your code and resolves your
import
statements intopypi
packages- Supported options in your root
requirements.txt
file are respected, such as--extra-index-url https://test.pypi.org/simple/
- Supported options in your root
- Dependencies are installed to and deployed with your Lambda
- For debugging purposes, the automatically generated
requirements.txt
can be found in your Lambda’svendor/
dir (until the next deployment or Sandbox run)- Example path:
src/http/get-index/vendor/_arc_autoinstall/requirements.txt
- Example path:
Using Architect’s Lambda treeshaking, generally most developers do not need to spend any time managing individual dependencies across their project’s many functions.
Automatically install architect-functions
:
# src/http/get-index/lambda.py
import arc
def handler(req):
return {"ok": True}
Native packages
By default, pip
attempts to install packages as wheels that match the platform it’s running on. For example: if you’re running a Mac, pip
will attempt to download the Mac wheel of the package you’re installing. This is great for working locally in Sandbox.
During deployment to AWS, Architect’s dependency hydration makes a best-effort attempt to guide pip
to download wheels compatible with AWS Linux 2 (AL2), the underlying OS of Lambda (and many other AWS compute services). However, not all packages publish AL2 compatible wheels. Packages that do not publish source distributions, or that incorrectly tag glibc
versions in their wheel distributions, for example, may have issues running in AWS.
Due to this potential variability in dependency compatibility, we advise thoroughly testing your Python deployments in staging before promoting to production.
Manual dependency management
Sometimes you may need fine-grained control over an individual function’s dependencies. In these cases, Architect enables per-Lambda dependency management by way of a requirements.txt
file in your Lambda’s folder.
If a requirements.txt
file is found in your Lambda, Architect’s treeshaking functionality will be automatically disabled for that specific Lambda, and your requirements.txt
file will be in complete control of that Lambda’s dependencies.
Relative modules & code sharing
Individual Lambdas can import
code from within their own path, but it is important to understand they cannot make use of local files that descend into other Lambdas. Attempting to do so will not work once deployed.
To share code across multiple Lambdas, please make use of @shared
and @views
, and refer to our guide on code sharing.
For example, assume the following src/http/get-index/lambda.py
handler:
# This is ok if it exists in the root requirements.txt file
import arc # → architect-functions
# This will fail
import ..foo
# This will work (if present, of course)
import .foo
# This is also ok (if foo exists in @shared)
import vendor.shared.foo
Ruby
Automated dependency treeshaking
Per the runtime support matrix, Architect does not support automated dependency management for Ruby Lambdas at this time.
Manual dependency management
Architect installs per-Lambda dependencies with bundle
according to each Gemfile
(and Gemfile.lock
) found in each function directory.
Relative modules & code sharing
Individual Lambdas can require
code from within their own path, but it is important to understand they cannot make use of local files that descend into other Lambdas. Attempting to do so will not work once deployed.
To share code across multiple Lambdas, please make use of @shared
and @views
, and refer to our guide on code sharing.
For example, assume the following src/http/get-index/lambda.rb
handler:
# Initialize Bundler
require 'bundler/setup'
# This is ok if it exists in the Lambda's Gemfile
require 'architect/functions'
# This will fail
require '../foo'
# This will work (if present, of course)
require './foo'
# This is also ok (if foo exists in @shared)
require './vendor/shared/foo'
Deployment configuration
Prior to deploying, it is recommended to configure Bundler to work in a Lambda environment.
You’ll need to let Bundler know about Lambda’s platform architecture by adding an entry to the Gemfile.lock
(see below).
Additionally, Bundler often tries to write to the filesystem at runtime. Freeze the bundle by setting the BUNDLE_FROZEN
environment variable to 1
.
# Declare the Lambda platform; assumes you have not configured your Lambda to use ARM
bundle lock --add-platform x86_64-linux
# Use Architect to set a Bundler-specific env var for staging & production
npx arc env -a -e staging BUNDLE_FROZEN 1
npx arc env -a -e production BUNDLE_FROZEN 1