SiamCafe · Blog
CDK Construct Event Driven Design — สร้าง
บทความ

CDK Construct Event Driven Design — สร้าง

เผยแพร่ 28 พฤษภาคม 2569

CDK Event Driven

AWS CDK Construct Event Driven Architecture EventBridge SQS Lambda Step Functions Serverless TypeScript Python Production

PatternAWS ServicesUse CaseCDK Construct Level
Fan-outEventBridge → Lambda × NNotify multiple servicesL3 (Pattern)
Queue ProcessingSQS → LambdaAsync job processingL2 (SqsEventSource)
SagaStep FunctionsDistributed transactionsL3 (Custom)
CQRSAPI GW + DynamoDB StreamSeparate read/writeL3 (Custom)
Event SourcingKinesis + DynamoDBAudit trail, replayL3 (Custom)
DLQSQS DLQ + LambdaFailed event handlingL2 (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