CDK Construct Event Driven Design — สร้าง
CDK Event Driven
AWS CDK Construct Event Driven Architecture EventBridge SQS Lambda Step Functions Serverless TypeScript Python Production
| Pattern | AWS Services | Use Case | CDK Construct Level |
|---|---|---|---|
| Fan-out | EventBridge → Lambda × N | Notify multiple services | L3 (Pattern) |
| Queue Processing | SQS → Lambda | Async job processing | L2 (SqsEventSource) |
| Saga | Step Functions | Distributed transactions | L3 (Custom) |
| CQRS | API GW + DynamoDB Stream | Separate read/write | L3 (Custom) |
| Event Sourcing | Kinesis + DynamoDB | Audit trail, replay | L3 (Custom) |
| DLQ | SQS DLQ + Lambda | Failed event handling | L2 (deadLetterQueue) |
CDK Constructs
# === CDK Event Driven Construct ===
# TypeScript CDK Example
# import * as cdk from 'aws-cdk-lib';
# import * as lambda from 'aws-cdk-lib/aws-lambda';
# import * as sqs from 'aws-cdk-lib/aws-sqs';
# import * as events from 'aws-cdk-lib/aws-events';
# import * as targets from 'aws-cdk-lib/aws-events-targets';
# import * as sources from 'aws-cdk-lib/aws-lambda-event-sources';
# import { Construct } from 'constructs';
#
# interface OrderProcessorProps {
# environment: string;
# maxConcurrency: number;
# retryAttempts: number;
# }
#
# class OrderProcessor extends Construct {
# public readonly queue: sqs.Queue;
# public readonly dlq: sqs.Queue;
# public readonly processor: lambda.Function;
#
# constructor(scope: Construct, id: string, props: OrderProcessorProps) {
# super(scope, id);
#
# // Dead Letter Queue
# this.dlq = new sqs.Queue(this, 'DLQ', {
# retentionPeriod: cdk.Duration.days(14),
# visibilityTimeout: cdk.Duration.seconds(300),
# });
#
# // Main Queue
# this.queue = new sqs.Queue(this, 'Queue', {
# visibilityTimeout: cdk.Duration.seconds(300),
# deadLetterQueue: {
# queue: this.dlq,
# maxReceiveCount: props.retryAttempts,
# },
# });
#
# // Lambda Processor
# this.processor = new lambda.Function(this, 'Fn', {
# runtime: lambda.Runtime.NODEJS_20_X,
# handler: 'index.handler',
# code: lambda.Code.fromAsset('lambda/order-processor'),
# timeout: cdk.Duration.seconds(60),
# memorySize: 512,
# reservedConcurrentExecutions: props.maxConcurrency,
# environment: { ENV: props.environment, QUEUE_URL: this.queue.queueUrl },
# });
#
# // Connect SQS → Lambda
# this.processor.addEventSource(new sources.SqsEventSource(this.queue, {
# batchSize: 10,
# maxBatchingWindow: cdk.Duration.seconds(5),
# }));
#
# // EventBridge Rule → SQS
# new events.Rule(this, 'OrderRule', {
# eventPattern: { source: ['com.myapp.orders'], detailType: ['OrderCreated'] },
# targets: [new targets.SqsQueue(this.queue)],
# });
# }
# }
from dataclasses import dataclass
@dataclass
class ConstructLevel:
level: str
name: str
description: str
example: str
when_to_use: str
levels = [
ConstructLevel("L1", "CFN Resources",
"CloudFormation Resource ตรงๆ ตั้งทุก Property เอง",
"CfnBucket, CfnFunction, CfnQueue",
"ต้องการ Control ทุก Property ไม่มี L2 สำหรับ Service นั้น"),
ConstructLevel("L2", "AWS Constructs",
"Higher-level มี Default ที่ดี Grant Permission ง่าย",
"s3.Bucket, lambda.Function, sqs.Queue",
"ใช้บ่อยที่สุด 90% ของงาน Default ดี ปรับได้"),
ConstructLevel("L3", "Patterns",
"รวมหลาย L2 เข้าด้วยกัน เป็น Solution Pattern",
"LambdaRestApi, QueueProcessingFargateService",
"Pattern ซ้ำๆ สร้างเป็น Reusable Construct"),
]
print("=== CDK Construct Levels ===")
for l in levels:
print(f" [{l.level}] {l.name}")
print(f" Description: {l.description}")
print(f" Example: {l.example}")
print(f" When: {l.when_to_use}")
Event Patterns
# === Event Driven Patterns ===
# Fan-out with EventBridge
# const bus = new events.EventBus(this, 'AppBus');
#
# // Rule 1: Order → Inventory Lambda
# new events.Rule(this, 'InventoryRule', {
# eventBus: bus,
# eventPattern: { detailType: ['OrderCreated'] },
# targets: [new targets.LambdaFunction(inventoryFn)],
# });
#
# // Rule 2: Order → Notification Lambda
# new events.Rule(this, 'NotifyRule', {
# eventBus: bus,
# eventPattern: { detailType: ['OrderCreated'] },
# targets: [new targets.LambdaFunction(notifyFn)],
# });
#
# // Rule 3: Order → Analytics SQS
# new events.Rule(this, 'AnalyticsRule', {
# eventBus: bus,
# eventPattern: { detailType: ['OrderCreated'] },
# targets: [new targets.SqsQueue(analyticsQueue)],
# });
@dataclass
class EventPattern:
pattern: str
services: str
flow: str
benefit: str
cdk_construct: str
patterns = [
EventPattern("Fan-out",
"EventBridge → Lambda/SQS/SNS × N",
"1 Event → หลาย Consumer พร้อมกัน",
"Loose coupling เพิ่ม Consumer ไม่กระทบ Producer",
"events.Rule + targets.LambdaFunction/SqsQueue"),
EventPattern("Queue Processing",
"SQS → Lambda (Event Source Mapping)",
"Producer → SQS Queue → Lambda Poll → Process",
"Buffer load spikes, Retry, DLQ, Batch processing",
"sqs.Queue + lambda.Function + SqsEventSource"),
EventPattern("Saga (Orchestration)",
"Step Functions → Lambda × N",
"Step Function ควบคุม Flow: Pay → Ship → Notify",
"Transaction management, Compensation, Visibility",
"stepfunctions.StateMachine + tasks.LambdaInvoke"),
EventPattern("CQRS",
"API GW → Lambda → DynamoDB → Stream → Lambda → Read DB",
"Write ผ่าน Command API, Read ผ่าน Query API แยกกัน",
"Scale Read/Write แยก Optimize แต่ละด้าน",
"apigateway.RestApi + dynamodb.Table + DynamoEventSource"),
EventPattern("Dead Letter Queue",
"SQS Main Queue → DLQ after N retries",
"Failed messages ไป DLQ → Lambda/Dashboard วิเคราะห์",
"ไม่สูญเสีย Event ที่ Fail วิเคราะห์ Root Cause ได้",
"sqs.Queue({ deadLetterQueue: { queue, maxReceiveCount } })"),
]
print("=== Event Patterns ===")
for p in patterns:
print(f" [{p.pattern}] {p.services}")
print(f" Flow: {p.flow}")
print(f" Benefit: {p.benefit}")
print(f" CDK: {p.cdk_construct}")
Testing
# === CDK Testing ===
# import { Template, Match } from 'aws-cdk-lib/assertions';
# import { App } from 'aws-cdk-lib';
# import { OrderProcessorStack } from '../lib/order-processor-stack';
#
# test('Queue created with DLQ', () => {
# const app = new App();
# const stack = new OrderProcessorStack(app, 'Test');
# const template = Template.fromStack(stack);
#
# template.hasResourceProperties('AWS::SQS::Queue', {
# RedrivePolicy: Match.objectLike({
# maxReceiveCount: 3,
# }),
# });
# });
#
# test('Lambda has correct timeout', () => {
# template.hasResourceProperties('AWS::Lambda::Function', {
# Timeout: 60,
# MemorySize: 512,
# });
# });
#
# test('EventBridge rule targets queue', () => {
# template.hasResourceProperties('AWS::Events::Rule', {
# EventPattern: Match.objectLike({
# 'detail-type': ['OrderCreated'],
# }),
# });
# });
@dataclass
class TestType:
test: str
tool: str
what: str
example: str
tests = [
TestType("Snapshot Test",
"CDK Assertions Template.fromStack",
"ตรวจ Template ไม่เปลี่ยนจากที่คาดหวัง",
"expect(template.toJSON()).toMatchSnapshot()"),
TestType("Fine-grained Assertion",
"template.hasResourceProperties",
"ตรวจ Resource มี Property ที่ถูกต้อง",
"hasResourceProperties('AWS::SQS::Queue', {...})"),
TestType("Resource Count",
"template.resourceCountIs",
"ตรวจจำนวน Resource ที่สร้าง",
"resourceCountIs('AWS::Lambda::Function', 3)"),
TestType("cdk-nag",
"cdk-nag NagPacks",
"ตรวจ Security Compliance Best Practices",
"Aspects.of(app).add(new AwsSolutionsChecks())"),
TestType("Integration Test",
"AWS SDK + deployed stack",
"ทดสอบ Event Flow จริงบน AWS",
"Put event → Check Lambda executed → Verify output"),
]
print("=== CDK Testing ===")
for t in tests:
print(f" [{t.test}] Tool: {t.tool}")
print(f" What: {t.what}")
print(f" Example: {t.example}")
เคล็ดลับ
- L2: ใช้ L2 Construct เป็นหลัก Default ดี Grant Permission ง่าย
- DLQ: ทุก Queue ต้องมี DLQ ไม่มีข้อยกเว้น ป้องกัน Event หาย
- EventBridge: ใช้ EventBridge เป็น Central Event Router ไม่ใช่ SNS
- Test: เขียน CDK Assertion Test + cdk-nag ทุก Stack
- Reuse: สร้าง L3 Construct สำหรับ Pattern ที่ใช้ซ้ำ Publish npm
AWS CDK คืออะไร
IaC Framework TypeScript Python Java Construct L1 L2 L3 CloudFormation AWS Service CDK CLI Deploy synth diff Reusable npm