Skip to content

Commit f13a376

Browse files
author
Sebastien Stormacq
committed
address co-pilot suggestions
1 parent 23cfa83 commit f13a376

File tree

2 files changed

+77
-18
lines changed

2 files changed

+77
-18
lines changed

Examples/MultiTenant/README.md

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The example consists of:
3434
1. **TenantData** - Immutable struct tracking tenant information:
3535
- `tenantID`: Unique identifier for the tenant
3636
- `requestCount`: Total number of requests from this tenant
37-
- `firstRequest`: ISO 8601 timestamp of the first request
37+
- `firstRequest`: Unix timestamp (seconds since epoch) of the first request
3838
- `requests`: Array of individual request records
3939

4040
2. **TenantDataStore** - Actor-based storage providing thread-safe access to tenant data across invocations
@@ -71,29 +71,52 @@ actor TenantDataStore {
7171

7272
// Lambda handler extracts tenant ID from context
7373
let runtime = LambdaRuntime {
74-
(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in
74+
(event: APIGatewayRequest, context: LambdaContext) -> APIGatewayResponse in
7575

7676
guard let tenantID = context.tenantID else {
77-
return APIGatewayV2Response(statusCode: .badRequest, body: "No Tenant ID provided")
77+
return APIGatewayResponse(statusCode: .badRequest, body: "No Tenant ID provided")
7878
}
7979

8080
// Process request for this tenant
8181
let currentData = await tenants[tenantID] ?? TenantData(tenantID: tenantID)
8282
let updatedData = currentData.addingRequest()
8383
await tenants.update(id: tenantID, data: updatedData)
8484

85-
return try APIGatewayV2Response(statusCode: .ok, encodableBody: updatedData)
85+
return try APIGatewayResponse(statusCode: .ok, encodableBody: updatedData)
8686
}
8787
```
8888

8989
## Configuration
9090

9191
### SAM Template (template.yaml)
9292

93-
The function is configured with tenant isolation mode in the SAM template:
93+
The function is configured with tenant isolation mode and API Gateway parameter mapping in the SAM template:
9494

9595
```yaml
96-
APIGatewayLambda:
96+
# API Gateway REST API with parameter mapping
97+
MultiTenantApi:
98+
Type: AWS::Serverless::Api
99+
Properties:
100+
StageName: Prod
101+
DefinitionBody:
102+
openapi: 3.0.1
103+
paths:
104+
/:
105+
get:
106+
parameters:
107+
- name: tenant-id
108+
in: query
109+
required: true
110+
x-amazon-apigateway-integration:
111+
type: aws_proxy
112+
httpMethod: POST
113+
uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MultiTenantLambda.Arn}/invocations
114+
# Map query parameter to Lambda tenant header
115+
requestParameters:
116+
integration.request.header.X-Amz-Tenant-Id: method.request.querystring.tenant-id
117+
118+
# Lambda function with tenant isolation
119+
MultiTenantLambda:
97120
Type: AWS::Serverless::Function
98121
Properties:
99122
Runtime: provided.al2023
@@ -102,17 +125,31 @@ APIGatewayLambda:
102125
# Enable tenant isolation mode
103126
TenancyConfig:
104127
TenantIsolationMode: PER_TENANT
105-
Events:
106-
HttpApiEvent:
107-
Type: HttpApi
108128
```
109129
110130
### Key Configuration Points
111131
112132
- **TenancyConfig.TenantIsolationMode**: Set to `PER_TENANT` to enable tenant isolation
133+
- **Parameter Mapping**: API Gateway maps the `tenant-id` query parameter to the `X-Amz-Tenant-Id` header required by Lambda
134+
- **REST API**: Uses REST API (not HTTP API) to support request parameter mapping
135+
- **OpenAPI Definition**: Defines the integration using OpenAPI 3.0 specification for fine-grained control
113136
- **Immutable property**: Tenant isolation can only be enabled when creating a new function
114137
- **Required tenant-id**: All invocations must include a tenant identifier
115138

139+
### Why Parameter Mapping is Required
140+
141+
Lambda's tenant isolation feature requires the tenant ID to be passed via the `X-Amz-Tenant-Id` header. When using API Gateway:
142+
143+
1. **Client sends request** with `tenant-id` as a query parameter
144+
2. **API Gateway transforms** the query parameter into the `X-Amz-Tenant-Id` header
145+
3. **Lambda receives** the header and routes to the appropriate tenant-isolated environment
146+
147+
This mapping is configured in the `x-amazon-apigateway-integration` section using:
148+
```yaml
149+
requestParameters:
150+
integration.request.header.X-Amz-Tenant-Id: method.request.querystring.tenant-id
151+
```
152+
116153
## Deployment
117154

118155
### Prerequisites
@@ -140,14 +177,34 @@ APIGatewayLambda:
140177

141178
### Using API Gateway
142179

143-
The tenant ID is passed as a query parameter:
180+
The tenant ID is passed as a query parameter. API Gateway automatically maps it to the `X-Amz-Tenant-Id` header:
144181

145182
```bash
146183
# Request from tenant "alice"
147-
curl "https://your-api-id.execute-api.us-east-1.amazonaws.com?tenant-id=alice"
184+
curl "https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod?tenant-id=alice"
148185
149-
# Request from tenant "bob"
150-
curl "https://your-api-id.execute-api.us-east-1.amazonaws.com?tenant-id=bob"
186+
# Request from tenant "bob"
187+
curl "https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod?tenant-id=bob"
188+
189+
# Multiple requests from the same tenant will reuse the execution environment
190+
for i in {1..5}; do
191+
curl "https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod?tenant-id=alice"
192+
done
193+
```
194+
195+
### Using AWS CLI (Direct Lambda Invocation)
196+
197+
For direct Lambda invocation without API Gateway:
198+
199+
```bash
200+
# Synchronous invocation
201+
aws lambda invoke \
202+
--function-name MultiTenantLambda \
203+
--tenant-id alice \
204+
response.json
205+
206+
# View the response
207+
cat response.json
151208
```
152209

153210
### Expected Response
@@ -156,24 +213,26 @@ curl "https://your-api-id.execute-api.us-east-1.amazonaws.com?tenant-id=bob"
156213
{
157214
"tenantID": "alice",
158215
"requestCount": 3,
159-
"firstRequest": "2024-01-15T10:30:00Z",
216+
"firstRequest": "1705320000.123456",
160217
"requests": [
161218
{
162219
"requestNumber": 1,
163-
"timestamp": "2024-01-15T10:30:00Z"
220+
"timestamp": "1705320000.123456"
164221
},
165222
{
166223
"requestNumber": 2,
167-
"timestamp": "2024-01-15T10:31:15Z"
224+
"timestamp": "1705320075.789012"
168225
},
169226
{
170227
"requestNumber": 3,
171-
"timestamp": "2024-01-15T10:32:30Z"
228+
"timestamp": "1705320150.345678"
172229
}
173230
]
174231
}
175232
```
176233

234+
**Note**: Timestamps are Unix epoch times (seconds since January 1, 1970) for cross-platform compatibility.
235+
177236
## How Tenant Isolation Works
178237

179238
1. **Request arrives** with a tenant identifier (via query parameter, header, or direct invocation)

Examples/MultiTenant/template.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Resources:
6767
Environment:
6868
Variables:
6969
# by default, AWS Lambda runtime produces no log
70-
# use `LOG_LEVEL: debug` for for lifecycle and event handling information
70+
# use `LOG_LEVEL: debug` for lifecycle and event handling information
7171
# use `LOG_LEVEL: trace` for detailed input event information
7272
LOG_LEVEL: trace
7373
Events:

0 commit comments

Comments
 (0)