engineering-handbook/sam-project-layout.md

101 lines
3.3 KiB
Markdown
Raw Permalink Normal View History

# SAM Project Layout
Remaining path for stacks that already use SAM. New deployables use [terraform-project-layout.md](terraform-project-layout.md) and [hcp-terraform.md](hcp-terraform.md).
## Standard Directory Structure
```
project-name/
├── template.yaml # SAM template at root
├── samconfig.toml # Deploy config (gitignored)
├── samconfig.toml.example # Template for onboarding (committed)
├── src/
│ ├── function_name/
│ │ ├── app.py # Lambda handler
│ │ └── requirements.txt # Per-function dependencies
│ └── shared/ # Shared layer code (if needed)
└── .gitignore
```
## File Purposes
### `template.yaml`
The SAM/CloudFormation template. Lives at the project root. Defines all Lambda functions, IAM roles, API Gateway endpoints, DynamoDB tables, and other resources.
### `samconfig.toml`
Contains real ARNs, S3 bucket names, and deploy parameters. **Gitignored** because it varies per environment and may contain account-specific values.
### `samconfig.toml.example`
A committed template showing the expected structure and parameter names. New contributors copy this to `samconfig.toml` and fill in their values.
### `src/function_name/`
One directory per Lambda function. Each contains its own handler (`app.py`) and dependencies (`requirements.txt`). This keeps functions independently deployable and avoids bloating one function with another's dependencies.
### `src/shared/`
Optional. Used for code shared across multiple functions, typically deployed as a Lambda layer. See the next section for layout details.
## Lambda Layers
When sharing code across functions via a layer, the source layout matters because SAM transforms `ContentUri` depending on whether `BuildMethod` is set.
### Correct layout (with `BuildMethod`)
```
src/shared/
├── shared/ # Package goes here directly — SAM wraps in python/ at build time
│ ├── __init__.py
│ └── utils.py
└── requirements.txt # Layer-level pip dependencies
```
Template:
```yaml
SharedLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-stack-shared
ContentUri: src/shared/
CompatibleRuntimes:
- python3.12
CompatibleArchitectures:
- arm64
Metadata:
BuildMethod: python3.12
BuildArchitecture: arm64
```
### BuildMethod nesting gotcha
With `BuildMethod: python3.12`, SAM copies `ContentUri` into a `python/` subdirectory during build, then pip-installs `requirements.txt` deps into that same `python/` directory.
Do **not** include a `python/` wrapper in your source — SAM adds it. The wrong layout:
```
src/shared/
├── python/ # SAM wraps this again → python/python/shared/ — module unreachable
│ └── shared/
└── requirements.txt
```
A real production incident (~22 hours of outage) traced to this exact pattern when a refactor moved layer code under an extra `python/` directory.
### Without BuildMethod (raw zip)
If the layer has no pip dependencies and you omit `BuildMethod`, SAM zips `ContentUri` as-is — you DO need the `python/` wrapper. Reserve raw zip for layers that ship only Python source.
## Standard .gitignore
```
.aws-sam/
__pycache__/
*.pyc
.env
samconfig.toml
```