$npx -y skills add vibeeval/vibecosystem --skill aws-patternsLambda best practices, S3 event patterns, SQS/SNS fanout, and DynamoDB access patterns for serverless AWS architectures.
| 1 | # AWS Patterns |
| 2 | |
| 3 | Serverless and managed service patterns for AWS production workloads. |
| 4 | |
| 5 | ## Lambda Best Practices |
| 6 | |
| 7 | ```typescript |
| 8 | // Cold start optimization: keep handler thin, initialize outside handler |
| 9 | import { DynamoDBClient } from '@aws-sdk/client-dynamodb' |
| 10 | import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb' |
| 11 | |
| 12 | // Initialized ONCE per container (reused across invocations) |
| 13 | const client = DynamoDBDocumentClient.from(new DynamoDBClient({})) |
| 14 | |
| 15 | export const handler = async (event: APIGatewayProxyEvent) => { |
| 16 | try { |
| 17 | const userId = event.pathParameters?.id |
| 18 | if (!userId) { |
| 19 | return { statusCode: 400, body: JSON.stringify({ error: 'Missing user ID' }) } |
| 20 | } |
| 21 | |
| 22 | const result = await client.send(new GetCommand({ |
| 23 | TableName: process.env.USERS_TABLE!, |
| 24 | Key: { pk: `USER#${userId}`, sk: `PROFILE` } |
| 25 | })) |
| 26 | |
| 27 | if (!result.Item) { |
| 28 | return { statusCode: 404, body: JSON.stringify({ error: 'User not found' }) } |
| 29 | } |
| 30 | |
| 31 | return { |
| 32 | statusCode: 200, |
| 33 | headers: { 'Content-Type': 'application/json' }, |
| 34 | body: JSON.stringify(result.Item) |
| 35 | } |
| 36 | } catch (err) { |
| 37 | console.error('Handler error:', err) |
| 38 | return { statusCode: 500, body: JSON.stringify({ error: 'Internal server error' }) } |
| 39 | } |
| 40 | } |
| 41 | ``` |
| 42 | |
| 43 | ## S3 Event Processing |
| 44 | |
| 45 | ```typescript |
| 46 | // S3 → Lambda: process uploaded files |
| 47 | import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' |
| 48 | |
| 49 | const s3 = new S3Client({}) |
| 50 | |
| 51 | export const handler = async (event: S3Event) => { |
| 52 | for (const record of event.Records) { |
| 53 | const bucket = record.s3.bucket.name |
| 54 | const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')) |
| 55 | const size = record.s3.object.size |
| 56 | |
| 57 | // Guard: skip non-image files or oversized uploads |
| 58 | if (size > 10_000_000) { |
| 59 | console.warn(`Skipping oversized file: ${key} (${size} bytes)`) |
| 60 | continue |
| 61 | } |
| 62 | |
| 63 | const response = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key })) |
| 64 | const body = await response.Body!.transformToByteArray() |
| 65 | |
| 66 | await processImage(body, key) |
| 67 | |
| 68 | console.log(`Processed ${key} (${size} bytes)`) |
| 69 | } |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | ## SQS/SNS Fanout Pattern |
| 74 | |
| 75 | ```typescript |
| 76 | // SNS → multiple SQS queues (fanout to parallel consumers) |
| 77 | |
| 78 | // Publisher: send to SNS topic |
| 79 | import { SNSClient, PublishCommand } from '@aws-sdk/client-sns' |
| 80 | |
| 81 | const sns = new SNSClient({}) |
| 82 | |
| 83 | async function publishOrderEvent(order: Order): Promise<void> { |
| 84 | await sns.send(new PublishCommand({ |
| 85 | TopicArn: process.env.ORDER_EVENTS_TOPIC!, |
| 86 | Message: JSON.stringify(order), |
| 87 | MessageAttributes: { |
| 88 | eventType: { DataType: 'String', StringValue: 'order.created' }, |
| 89 | region: { DataType: 'String', StringValue: order.region } |
| 90 | } |
| 91 | })) |
| 92 | } |
| 93 | |
| 94 | // Consumer: SQS Lambda (one per subscriber: email, analytics, inventory) |
| 95 | export const emailHandler = async (event: SQSEvent) => { |
| 96 | for (const record of event.Records) { |
| 97 | const order = JSON.parse(record.body) as Order |
| 98 | |
| 99 | try { |
| 100 | await sendOrderConfirmation(order) |
| 101 | } catch (err) { |
| 102 | console.error(`Failed to send email for order ${order.id}:`, err) |
| 103 | throw err // Message returns to queue for retry (DLQ after maxReceiveCount) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | ``` |
| 108 | |
| 109 | ## DynamoDB Single-Table Design |
| 110 | |
| 111 | ```typescript |
| 112 | // Access patterns drive table design, not entity relationships |
| 113 | |
| 114 | // Table: pk (partition key) + sk (sort key) + GSI1PK + GSI1SK |
| 115 | // Entities: User, Order, OrderItem - all in one table |
| 116 | |
| 117 | const AccessPatterns = { |
| 118 | // Get user profile |
| 119 | getUserProfile: (userId: string) => ({ |
| 120 | pk: `USER#${userId}`, |
| 121 | sk: `PROFILE` |
| 122 | }), |
| 123 | |
| 124 | // Get user's orders (sorted by date) |
| 125 | getUserOrders: (userId: string) => ({ |
| 126 | pk: `USER#${userId}`, |
| 127 | sk: { begins_with: 'ORDER#' } // sk: ORDER#2025-01-15#orderId |
| 128 | }), |
| 129 | |
| 130 | // Get order with items |
| 131 | getOrderWithItems: (orderId: string) => ({ |
| 132 | pk: `ORDER#${orderId}`, |
| 133 | sk: { begins_with: '' } // sk: METADATA, ITEM#productId |
| 134 | }), |
| 135 | |
| 136 | // Get orders by status (GSI1) |
| 137 | getOrdersByStatus: (status: string) => ({ |
| 138 | GSI1PK: `STATUS#${status}`, |
| 139 | GSI1SK: { begins_with: '' } // GSI1SK: date#orderId |
| 140 | }) |
| 141 | } |
| 142 | |
| 143 | // Write: transactional multi-item write |
| 144 | import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb' |
| 145 | |
| 146 | async function createOrder(order: Order): Promise<void> { |
| 147 | const items = [ |
| 148 | // Order metadata |
| 149 | { |
| 150 | Put: { |
| 151 | TableName: process.env.TABLE!, |
| 152 | Item: { |
| 153 | pk: `ORDER#${order.id}`, |
| 154 | sk: 'METADATA', |
| 155 | GSI1PK: `STATUS#${order.status}`, |
| 156 | GSI1SK: `${order.createdAt}#${order.id}`, |
| 157 | ...order |
| 158 | } |
| 159 | } |
| 160 | }, |
| 161 | // User's order reference |
| 162 | { |
| 163 | Put: { |
| 164 | TableName: process.env.TABLE!, |
| 165 | Item: { |
| 166 | pk: `USER#${order.userId}`, |
| 167 | sk: `ORDER#${order.createdAt}#${order.id}`, |
| 168 | orderId: order.id, |
| 169 | total: order.total, |
| 170 | status: order.status |