AWS for Retail and E-commerce: Building Scalable Online Stores | Forrict Skip to main content
Cloud Architecture E-commerce AWS Services

AWS for Retail and E-commerce: Building Scalable Online Stores

Forrict Team
AWS for Retail and E-commerce: Building Scalable Online Stores
Comprehensive guide to building scalable e-commerce platforms on AWS with DynamoDB, CloudFront, and Auto Scaling for peak traffic handling.

The Dutch e-commerce market is booming, with online retail sales exceeding €30 billion annually. Major players like Bol.com and Coolblue have set the standard for customer experience, requiring infrastructure that can handle millions of visitors during peak events like Black Friday, Sinterklaas, and Cyber Monday. Amazon Web Services (AWS) provides the perfect foundation for building scalable, secure, and performant e-commerce platforms.

Why AWS for E-commerce?

The e-commerce landscape demands infrastructure that can scale from handling hundreds to millions of concurrent users within minutes. Traditional hosting solutions struggle with this elasticity, leading to site crashes during peak shopping periods, lost revenue, and damaged brand reputation. AWS offers a comprehensive suite of services specifically designed for e-commerce workloads:

Scalability: Auto Scaling groups can automatically add or remove compute capacity based on traffic patterns, ensuring your site remains responsive during unexpected traffic spikes.

Global Delivery: CloudFront’s edge locations across Europe and worldwide deliver content with millisecond latency, crucial for converting international customers.

Security and Compliance: AWS services are compliant with PCI-DSS requirements for payment processing, GDPR for European customer data, and provide built-in DDoS protection through AWS Shield.

Cost Optimization: Pay-per-use pricing means you only pay for the resources you consume, avoiding over-provisioning for peak capacity that sits idle most of the year.

Leading Dutch retailers have proven this approach works. When Coolblue expanded to Belgium and Germany, they leveraged AWS’s multi-region capabilities to maintain consistent performance across borders. Similarly, smaller Dutch fashion retailers and specialty stores have used AWS to compete with larger players by delivering enterprise-grade customer experiences without enterprise-scale infrastructure costs.

E-commerce Architecture on AWS

A modern e-commerce platform consists of several key components: product catalog management, shopping cart and session handling, order processing, payment integration, content delivery, and analytics. Let’s explore a reference architecture that handles these requirements efficiently.

Core Architecture Components

The foundation of a scalable e-commerce platform on AWS typically includes:

Frontend Delivery: Static website assets (HTML, CSS, JavaScript) hosted in Amazon S3 and distributed globally via CloudFront CDN. This approach dramatically reduces hosting costs while improving performance.

Application Layer: API Gateway provides the entry point for all backend operations, routing requests to Lambda functions (serverless) or containerized applications running on ECS/EKS (for more complex business logic).

Data Layer: DynamoDB for product catalog, shopping carts, and user sessions; RDS for transactional order data; ElastiCache for frequently accessed data like featured products and pricing.

Search and Discovery: Amazon OpenSearch Service powers product search, faceted navigation, and recommendations, providing the fast, relevant results customers expect.

Payment Processing: Integration with payment providers through secure Lambda functions, with sensitive data never touching your application servers.

Here’s a complete AWS CDK example that implements the core infrastructure:

import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';

export class EcommerceStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC for ElastiCache and private resources
    const vpc = new ec2.Vpc(this, 'EcommerceVPC', {
      maxAzs: 2,
      natGateways: 1,
    });

    // S3 bucket for static website assets
    const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
      websiteIndexDocument: 'index.html',
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });

    // CloudFront distribution with caching optimizations
    const distribution = new cloudfront.Distribution(this, 'WebsiteDistribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(websiteBucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: new cloudfront.CachePolicy(this, 'CachePolicy', {
          defaultTtl: cdk.Duration.hours(24),
          minTtl: cdk.Duration.minutes(1),
          maxTtl: cdk.Duration.days(365),
          cookieBehavior: cloudfront.CacheCookieBehavior.none(),
          queryStringBehavior: cloudfront.CacheQueryStringBehavior.none(),
          headerBehavior: cloudfront.CacheHeaderBehavior.allowList('CloudFront-Viewer-Country'),
        }),
      },
      priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Europe and North America
    });

    // DynamoDB table for product catalog
    const productTable = new dynamodb.Table(this, 'ProductTable', {
      partitionKey: { name: 'productId', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecovery: true,
      stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
    });

    // Global Secondary Index for category-based queries
    productTable.addGlobalSecondaryIndex({
      indexName: 'CategoryIndex',
      partitionKey: { name: 'category', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'price', type: dynamodb.AttributeType.NUMBER },
    });

    // DynamoDB table for shopping carts
    const cartTable = new dynamodb.Table(this, 'CartTable', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING },
      timeToLiveAttribute: 'expiryTime',
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    // DynamoDB table for orders
    const orderTable = new dynamodb.Table(this, 'OrderTable', {
      partitionKey: { name: 'orderId', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'timestamp', type: dynamodb.AttributeType.NUMBER },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecovery: true,
    });

    // ElastiCache Redis cluster for session and product caching
    const subnetGroup = new elasticache.CfnSubnetGroup(this, 'CacheSubnetGroup', {
      description: 'Subnet group for Redis cache',
      subnetIds: vpc.privateSubnets.map(subnet => subnet.subnetId),
    });

    const securityGroup = new ec2.SecurityGroup(this, 'CacheSecurityGroup', {
      vpc,
      description: 'Security group for Redis cache',
      allowAllOutbound: true,
    });

    const redisCache = new elasticache.CfnReplicationGroup(this, 'RedisCache', {
      replicationGroupDescription: 'Redis cache for e-commerce',
      engine: 'redis',
      cacheNodeType: 'cache.r6g.large',
      numCacheClusters: 2,
      automaticFailoverEnabled: true,
      cacheSubnetGroupName: subnetGroup.ref,
      securityGroupIds: [securityGroup.securityGroupId],
      multiAzEnabled: true,
    });

    // Lambda function for product API
    const productFunction = new lambda.Function(this, 'ProductFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/product'),
      environment: {
        PRODUCT_TABLE: productTable.tableName,
        REDIS_ENDPOINT: redisCache.attrPrimaryEndPointAddress,
      },
      vpc: vpc,
      timeout: cdk.Duration.seconds(30),
      memorySize: 1024,
    });

    productTable.grantReadWriteData(productFunction);
    securityGroup.addIngressRule(
      ec2.Peer.securityGroupId(productFunction.connections.securityGroups[0].securityGroupId),
      ec2.Port.tcp(6379),
      'Allow Lambda to access Redis'
    );

    // Lambda function for cart operations
    const cartFunction = new lambda.Function(this, 'CartFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/cart'),
      environment: {
        CART_TABLE: cartTable.tableName,
        PRODUCT_TABLE: productTable.tableName,
      },
      timeout: cdk.Duration.seconds(30),
    });

    cartTable.grantReadWriteData(cartFunction);
    productTable.grantReadData(cartFunction);

    // Lambda function for order processing
    const orderFunction = new lambda.Function(this, 'OrderFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/order'),
      environment: {
        ORDER_TABLE: orderTable.tableName,
        CART_TABLE: cartTable.tableName,
        PRODUCT_TABLE: productTable.tableName,
      },
      timeout: cdk.Duration.seconds(60),
    });

    orderTable.grantReadWriteData(orderFunction);
    cartTable.grantReadWriteData(orderFunction);
    productTable.grantReadData(orderFunction);

    // API Gateway
    const api = new apigateway.RestApi(this, 'EcommerceAPI', {
      restApiName: 'E-commerce API',
      description: 'API for e-commerce operations',
      deployOptions: {
        throttlingRateLimit: 1000,
        throttlingBurstLimit: 2000,
        metricsEnabled: true,
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
      },
    });

    // API resources and methods
    const products = api.root.addResource('products');
    products.addMethod('GET', new apigateway.LambdaIntegration(productFunction));

    const productById = products.addResource('{productId}');
    productById.addMethod('GET', new apigateway.LambdaIntegration(productFunction));

    const cart = api.root.addResource('cart');
    cart.addMethod('GET', new apigateway.LambdaIntegration(cartFunction));
    cart.addMethod('POST', new apigateway.LambdaIntegration(cartFunction));
    cart.addMethod('PUT', new apigateway.LambdaIntegration(cartFunction));
    cart.addMethod('DELETE', new apigateway.LambdaIntegration(cartFunction));

    const orders = api.root.addResource('orders');
    orders.addMethod('POST', new apigateway.LambdaIntegration(orderFunction));
    orders.addMethod('GET', new apigateway.LambdaIntegration(orderFunction));

    // CloudFront behavior for API
    distribution.addBehavior('/api/*', new origins.RestApiOrigin(api), {
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
      cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
      allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
    });

    // Outputs
    new cdk.CfnOutput(this, 'DistributionDomain', {
      value: distribution.distributionDomainName,
      description: 'CloudFront distribution domain',
    });

    new cdk.CfnOutput(this, 'ApiEndpoint', {
      value: api.url,
      description: 'API Gateway endpoint',
    });

    new cdk.CfnOutput(this, 'RedisEndpoint', {
      value: redisCache.attrPrimaryEndPointAddress,
      description: 'Redis cache endpoint',
    });
  }
}

This CDK stack creates a production-ready e-commerce infrastructure with global content delivery, serverless API endpoints, and a highly scalable data layer.

Auto Scaling for Peak Traffic Events

Dutch consumers are highly engaged with seasonal shopping events. Black Friday sees traffic spikes of 300-500% compared to normal days, while Sinterklaas (early December) drives sustained high traffic for weeks. Your infrastructure must handle these patterns without manual intervention.

Traffic Pattern Analysis

Before implementing Auto Scaling, understand your traffic characteristics:

Predictable Spikes: Events like Black Friday, Sinterklaas, and summer sales happen on known dates. Use scheduled scaling to pre-warm capacity.

Unpredictable Surges: Viral social media posts, influencer mentions, or news coverage can drive sudden traffic. Reactive scaling handles these scenarios.

Regional Variations: Dutch shopping patterns differ from other markets. Sinterklaas is uniquely important in the Netherlands and Belgium, while Queen’s Day sales are Netherlands-specific.

Implementing Elastic Load Balancing and Auto Scaling

For containerized e-commerce applications, ECS with Auto Scaling provides the right balance of control and automation:

import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as autoscaling from 'aws-cdk-lib/aws-applicationautoscaling';

// ECS Cluster
const cluster = new ecs.Cluster(this, 'EcommerceCluster', {
  vpc: vpc,
  containerInsights: true,
});

// ECS Task Definition
const taskDefinition = new ecs.FargateTaskDefinition(this, 'AppTask', {
  memoryLimitMiB: 2048,
  cpu: 1024,
});

const container = taskDefinition.addContainer('app', {
  image: ecs.ContainerImage.fromRegistry('your-registry/ecommerce-app:latest'),
  logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'ecommerce' }),
  environment: {
    PRODUCT_TABLE: productTable.tableName,
    REDIS_ENDPOINT: redisCache.attrPrimaryEndPointAddress,
  },
  healthCheck: {
    command: ['CMD-SHELL', 'curl -f http://localhost/health || exit 1'],
    interval: cdk.Duration.seconds(30),
    timeout: cdk.Duration.seconds(5),
    retries: 3,
    startPeriod: cdk.Duration.seconds(60),
  },
});

container.addPortMappings({
  containerPort: 8080,
  protocol: ecs.Protocol.TCP,
});

// ECS Service
const service = new ecs.FargateService(this, 'EcommerceService', {
  cluster,
  taskDefinition,
  desiredCount: 2,
  minHealthyPercent: 50,
  maxHealthyPercent: 200,
  circuitBreaker: { rollback: true },
});

// Application Load Balancer
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
  vpc,
  internetFacing: true,
});

const listener = alb.addListener('Listener', {
  port: 443,
  certificates: [certificate], // ACM certificate
});

const targetGroup = listener.addTargets('ECS', {
  port: 8080,
  targets: [service],
  healthCheck: {
    path: '/health',
    interval: cdk.Duration.seconds(30),
    timeout: cdk.Duration.seconds(5),
    healthyThresholdCount: 2,
    unhealthyThresholdCount: 3,
  },
  deregistrationDelay: cdk.Duration.seconds(30),
});

// Auto Scaling based on CPU and Request Count
const scaling = service.autoScaleTaskCount({
  minCapacity: 2,
  maxCapacity: 50,
});

scaling.scaleOnCpuUtilization('CpuScaling', {
  targetUtilizationPercent: 70,
  scaleInCooldown: cdk.Duration.seconds(60),
  scaleOutCooldown: cdk.Duration.seconds(30),
});

scaling.scaleOnRequestCount('RequestScaling', {
  requestsPerTarget: 1000,
  targetGroup: targetGroup,
  scaleInCooldown: cdk.Duration.seconds(60),
  scaleOutCooldown: cdk.Duration.seconds(30),
});

// Scheduled scaling for Black Friday (last Friday of November)
scaling.scaleOnSchedule('BlackFridayScaleUp', {
  schedule: autoscaling.Schedule.cron({
    hour: '6',
    minute: '0',
    weekDay: 'FRI',
    month: 'NOV',
  }),
  minCapacity: 30,
  maxCapacity: 100,
});

scaling.scaleOnSchedule('BlackFridayScaleDown', {
  schedule: autoscaling.Schedule.cron({
    hour: '0',
    minute: '0',
    weekDay: 'SAT',
    month: 'NOV',
  }),
  minCapacity: 2,
  maxCapacity: 50,
});

// Scheduled scaling for Sinterklaas period (December 1-6)
scaling.scaleOnSchedule('SinterklaasScaleUp', {
  schedule: autoscaling.Schedule.cron({
    hour: '6',
    minute: '0',
    day: '1',
    month: 'DEC',
  }),
  minCapacity: 20,
  maxCapacity: 80,
});

scaling.scaleOnSchedule('SinterklaasScaleDown', {
  schedule: autoscaling.Schedule.cron({
    hour: '0',
    minute: '0',
    day: '7',
    month: 'DEC',
  }),
  minCapacity: 2,
  maxCapacity: 50,
});

This configuration ensures your application maintains performance during both predictable events (Black Friday, Sinterklaas) and unexpected traffic spikes.

CloudFront CDN for Global Delivery

Content Delivery Networks are essential for e-commerce performance. Studies show that 53% of mobile users abandon sites that take longer than 3 seconds to load, and a 100ms delay can reduce conversions by 7%. CloudFront delivers content from edge locations closest to your users, dramatically reducing latency.

CloudFront Architecture for E-commerce

An effective CloudFront setup for e-commerce includes multiple cache behaviors:

Static Assets: Images, CSS, JavaScript files cached for days or weeks with aggressive compression.

Product Pages: Dynamic content with shorter cache times (5-15 minutes) that updates when products change.

Personalized Content: User-specific content like shopping carts that bypasses caching entirely.

API Requests: Routed to your backend with connection pooling and keep-alive for efficiency.

Here’s an advanced CloudFront configuration optimized for Dutch and European customers:

import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as s3 from 'aws-cdk-lib/aws-s3';

// Origin Access Identity for secure S3 access
const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OAI');
websiteBucket.grantRead(originAccessIdentity);

// Custom cache policy for product pages
const productCachePolicy = new cloudfront.CachePolicy(this, 'ProductCachePolicy', {
  cachePolicyName: 'ProductPageCache',
  comment: 'Cache policy for product pages',
  defaultTtl: cdk.Duration.minutes(15),
  minTtl: cdk.Duration.minutes(1),
  maxTtl: cdk.Duration.hours(24),
  cookieBehavior: cloudfront.CacheCookieBehavior.allowList('session-id'),
  queryStringBehavior: cloudfront.CacheQueryStringBehavior.allowList('variant', 'color', 'size'),
  headerBehavior: cloudfront.CacheHeaderBehavior.allowList(
    'CloudFront-Viewer-Country',
    'CloudFront-Viewer-Currency',
    'Accept-Language'
  ),
  enableAcceptEncodingGzip: true,
  enableAcceptEncodingBrotli: true,
});

// Response headers policy for security and performance
const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'SecurityHeaders', {
  comment: 'Security headers for e-commerce',
  securityHeadersBehavior: {
    contentTypeOptions: { override: true },
    frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },
    referrerPolicy: {
      referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
      override: true
    },
    strictTransportSecurity: {
      accessControlMaxAge: cdk.Duration.days(365),
      includeSubdomains: true,
      override: true,
    },
    xssProtection: { protection: true, modeBlock: true, override: true },
  },
  customHeadersBehavior: {
    customHeaders: [
      { header: 'Cache-Control', value: 'public, max-age=900', override: false },
      { header: 'X-Country', value: 'NL', override: false },
    ],
  },
});

// Main distribution
const distribution = new cloudfront.Distribution(this, 'EcommerceDistribution', {
  comment: 'E-commerce website distribution',
  priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Europe and North America
  geoRestriction: cloudfront.GeoRestriction.allowlist('NL', 'BE', 'DE', 'FR', 'GB', 'US'),

  // Default behavior for static assets
  defaultBehavior: {
    origin: new origins.S3Origin(websiteBucket, {
      originAccessIdentity: originAccessIdentity,
    }),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
    compress: true,
    responseHeadersPolicy: responseHeadersPolicy,
  },

  // Additional behaviors
  additionalBehaviors: {
    // Product images with long cache
    '/images/*': {
      origin: new origins.S3Origin(websiteBucket, {
        originAccessIdentity: originAccessIdentity,
      }),
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      cachePolicy: new cloudfront.CachePolicy(this, 'ImageCache', {
        defaultTtl: cdk.Duration.days(30),
        maxTtl: cdk.Duration.days(365),
        minTtl: cdk.Duration.days(1),
      }),
      compress: true,
    },

    // Product pages with medium cache
    '/products/*': {
      origin: new origins.HttpOrigin(alb.loadBalancerDnsName, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
        connectionAttempts: 3,
        connectionTimeout: cdk.Duration.seconds(10),
        readTimeout: cdk.Duration.seconds(30),
        keepaliveTimeout: cdk.Duration.seconds(5),
      }),
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      cachePolicy: productCachePolicy,
      originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER,
      compress: true,
      responseHeadersPolicy: responseHeadersPolicy,
    },

    // Shopping cart - no caching
    '/cart/*': {
      origin: new origins.HttpOrigin(alb.loadBalancerDnsName, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
      }),
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
      cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
      originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER,
      allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
    },

    // API endpoints - no caching
    '/api/*': {
      origin: new origins.RestApiOrigin(api),
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
      cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
      originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER,
      allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
    },
  },

  // Custom error responses
  errorResponses: [
    {
      httpStatus: 404,
      responseHttpStatus: 404,
      responsePagePath: '/404.html',
      ttl: cdk.Duration.minutes(5),
    },
    {
      httpStatus: 500,
      responseHttpStatus: 500,
      responsePagePath: '/500.html',
      ttl: cdk.Duration.seconds(0),
    },
  ],

  // Enable logging
  enableLogging: true,
  logBucket: logBucket,
  logFilePrefix: 'cloudfront/',
});

This CloudFront configuration optimizes caching strategies based on content type while maintaining security and performance.

Product Catalog with DynamoDB

DynamoDB is ideal for product catalogs due to its ability to scale to millions of items, handle unpredictable traffic spikes, and provide single-digit millisecond latency. Unlike traditional relational databases, DynamoDB’s NoSQL design allows flexible schemas that accommodate varying product attributes.

Schema Design for Product Catalog

Effective DynamoDB schema design requires thinking about access patterns first:

# Product catalog schema design
import boto3
from decimal import Decimal
from datetime import datetime

dynamodb = boto3.resource('dynamodb', region_name='eu-west-1')
product_table = dynamodb.Table('Products')

# Example product item
product_item = {
    'productId': 'PROD-12345',  # Partition key
    'category': 'Electronics',
    'subcategory': 'Laptops',
    'brand': 'Dell',
    'name': 'Dell XPS 15',
    'description': 'Premium laptop with 15-inch display',
    'price': Decimal('1299.99'),
    'currency': 'EUR',
    'stock': 45,
    'warehouse': 'NL-AMSTERDAM',
    'attributes': {
        'processor': 'Intel i7-12700H',
        'ram': '16GB',
        'storage': '512GB SSD',
        'display': '15.6-inch FHD',
        'weight': '1.8kg',
    },
    'images': [
        'https://cdn.example.com/products/prod-12345-1.jpg',
        'https://cdn.example.com/products/prod-12345-2.jpg',
    ],
    'rating': Decimal('4.5'),
    'reviewCount': 127,
    'tags': ['laptop', 'premium', 'business'],
    'inStock': True,
    'createdAt': int(datetime.now().timestamp()),
    'updatedAt': int(datetime.now().timestamp()),
    'SEO': {
        'title': 'Dell XPS 15 Laptop - Premium Performance',
        'description': 'Buy Dell XPS 15 with Intel i7, 16GB RAM, 512GB SSD',
        'keywords': ['Dell XPS', 'laptop', 'premium laptop'],
    },
}

# Insert product
product_table.put_item(Item=product_item)

# Query products by category (using GSI)
response = product_table.query(
    IndexName='CategoryIndex',
    KeyConditionExpression='category = :cat',
    ExpressionAttributeValues={
        ':cat': 'Electronics'
    }
)

# Query with price filtering
response = product_table.query(
    IndexName='CategoryIndex',
    KeyConditionExpression='category = :cat AND price BETWEEN :min AND :max',
    ExpressionAttributeValues={
        ':cat': 'Electronics',
        ':min': Decimal('1000'),
        ':max': Decimal('1500'),
    }
)

Implementing Product Search with Caching

Combine DynamoDB with ElastiCache Redis for optimal performance:

import boto3
import redis
import json
from decimal import Decimal

# Initialize clients
dynamodb = boto3.resource('dynamodb', region_name='eu-west-1')
product_table = dynamodb.Table('Products')
redis_client = redis.Redis(
    host='your-redis-endpoint.cache.amazonaws.com',
    port=6379,
    decode_responses=True
)

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(DecimalEncoder, self).default(obj)

def get_product(product_id):
    """Get product with Redis caching"""
    cache_key = f'product:{product_id}'

    # Try cache first
    cached = redis_client.get(cache_key)
    if cached:
        print(f'Cache HIT for {product_id}')
        return json.loads(cached)

    # Cache miss - fetch from DynamoDB
    print(f'Cache MISS for {product_id}')
    response = product_table.get_item(Key={'productId': product_id})

    if 'Item' not in response:
        return None

    product = response['Item']

    # Store in cache for 1 hour
    redis_client.setex(
        cache_key,
        3600,
        json.dumps(product, cls=DecimalEncoder)
    )

    return product

def get_featured_products(category, limit=12):
    """Get featured products with list caching"""
    cache_key = f'featured:{category}:{limit}'

    # Try cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)

    # Fetch from DynamoDB
    response = product_table.query(
        IndexName='CategoryIndex',
        KeyConditionExpression='category = :cat',
        FilterExpression='inStock = :stock AND rating >= :rating',
        ExpressionAttributeValues={
            ':cat': category,
            ':stock': True,
            ':rating': Decimal('4.0'),
        },
        Limit=limit,
        ScanIndexForward=False,  # Descending order
    )

    products = response['Items']

    # Cache for 15 minutes
    redis_client.setex(
        cache_key,
        900,
        json.dumps(products, cls=DecimalEncoder)
    )

    return products

def update_product_price(product_id, new_price):
    """Update price and invalidate cache"""
    # Update in DynamoDB
    product_table.update_item(
        Key={'productId': product_id},
        UpdateExpression='SET price = :price, updatedAt = :time',
        ExpressionAttributeValues={
            ':price': Decimal(str(new_price)),
            ':time': int(datetime.now().timestamp()),
        }
    )

    # Invalidate cache
    redis_client.delete(f'product:{product_id}')

    # Also invalidate category caches
    product = product_table.get_item(Key={'productId': product_id})['Item']
    pattern = f'featured:{product["category"]}:*'
    for key in redis_client.scan_iter(match=pattern):
        redis_client.delete(key)

    print(f'Updated price for {product_id} and invalidated caches')

This implementation provides millisecond response times for frequently accessed products while ensuring data consistency when prices or inventory changes.

Payment Processing and PCI-DSS Compliance

Payment security is paramount for e-commerce. The Payment Card Industry Data Security Standard (PCI-DSS) requires strict controls around cardholder data. AWS provides tools to build PCI-DSS compliant payment flows without storing sensitive card data in your systems.

Tokenization Architecture

Modern e-commerce platforms use payment tokenization, where sensitive card data never touches your servers:

// Lambda function for payment processing with Stripe
import { APIGatewayProxyHandler } from 'aws-lambda';
import Stripe from 'stripe';
import { DynamoDB } from 'aws-sdk';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

const dynamodb = new DynamoDB.DocumentClient();
const ORDER_TABLE = process.env.ORDER_TABLE!;

export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const { orderId, paymentMethodId, amount, currency, customerId } = JSON.parse(event.body!);

    // Create payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100), // Convert to cents
      currency: currency || 'eur',
      payment_method: paymentMethodId,
      customer: customerId,
      confirm: true,
      metadata: {
        orderId: orderId,
        source: 'ecommerce-website',
      },
      // Require 3D Secure authentication for EU regulations
      payment_method_options: {
        card: {
          request_three_d_secure: 'automatic',
        },
      },
    });

    // Update order with payment status
    await dynamodb.update({
      TableName: ORDER_TABLE,
      Key: { orderId },
      UpdateExpression: 'SET paymentStatus = :status, paymentId = :paymentId, updatedAt = :time',
      ExpressionAttributeValues: {
        ':status': paymentIntent.status,
        ':paymentId': paymentIntent.id,
        ':time': Date.now(),
      },
    }).promise();

    // Return client secret for 3DS confirmation if needed
    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({
        success: true,
        paymentIntentId: paymentIntent.id,
        status: paymentIntent.status,
        clientSecret: paymentIntent.client_secret,
      }),
    };

  } catch (error: any) {
    console.error('Payment processing error:', error);

    return {
      statusCode: 400,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({
        success: false,
        error: error.message,
      }),
    };
  }
};

This approach ensures PCI-DSS compliance by:

  • Never storing raw card numbers in your systems
  • Using tokenization provided by payment processors
  • Implementing Strong Customer Authentication (SCA) required by European PSD2 regulations
  • Logging payment events for audit trails without exposing sensitive data

Personalization with Amazon Personalize

Modern e-commerce platforms provide personalized product recommendations that increase conversion rates by 20-30%. Amazon Personalize uses the same machine learning technology that powers Amazon.com’s recommendations.

Implementation Example

import boto3
import json

personalize = boto3.client('personalize', region_name='eu-west-1')
personalize_runtime = boto3.client('personalize-runtime', region_name='eu-west-1')
personalize_events = boto3.client('personalize-events', region_name='eu-west-1')

# Track user interactions
def track_user_event(user_id, item_id, event_type='ProductView'):
    """Send interaction event to Personalize"""
    event = {
        'userId': user_id,
        'itemId': item_id,
        'eventType': event_type,
        'sentAt': int(datetime.now().timestamp()),
    }

    personalize_events.put_events(
        trackingId='your-tracking-id',
        userId=user_id,
        sessionId=f'session-{user_id}',
        eventList=[event]
    )

# Get recommendations
def get_product_recommendations(user_id, num_results=10):
    """Get personalized recommendations for user"""
    response = personalize_runtime.get_recommendations(
        campaignArn='arn:aws:personalize:eu-west-1:123456789012:campaign/ecommerce-campaign',
        userId=user_id,
        numResults=num_results,
    )

    recommended_product_ids = [item['itemId'] for item in response['itemList']]
    return recommended_product_ids

# Get similar items
def get_similar_products(product_id, num_results=6):
    """Get products similar to given product"""
    response = personalize_runtime.get_recommendations(
        campaignArn='arn:aws:personalize:eu-west-1:123456789012:campaign/similar-items-campaign',
        itemId=product_id,
        numResults=num_results,
    )

    return [item['itemId'] for item in response['itemList']]

Real-World Architecture: Bol.com-Style Implementation

Let’s examine how to implement an architecture similar to Bol.com, the Netherlands’ largest online retailer:

Key Requirements

  • Handle 10+ million visitors per month
  • Process 50,000+ orders daily
  • Support 30+ million product listings
  • 99.9% uptime SLA
  • Multi-vendor marketplace capabilities

Architecture Diagram

┌─────────────────────────────────────────────────────────┐
│                    CloudFront CDN                        │
│         (Edge locations in Amsterdam, Frankfurt)         │
└────────────┬─────────────────────────────┬──────────────┘
             │                             │
             │                             │
    ┌────────▼────────┐          ┌────────▼────────────┐
    │   Static Assets │          │   Application LB    │
    │   (S3 + OAI)    │          │   (Multi-AZ)        │
    └─────────────────┘          └─────────┬───────────┘

                       ┌───────────────────┼───────────────────┐
                       │                   │                   │
              ┌────────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
              │   ECS Fargate   │ │  ECS Fargate   │ │  ECS Fargate   │
              │   (Web App)     │ │  (API Layer)   │ │  (Search)      │
              └────────┬────────┘ └───────┬────────┘ └───────┬────────┘
                       │                  │                   │
        ┌──────────────┴──────────────────┴─────────┬─────────┘
        │                                            │
┌───────▼──────────┐                      ┌─────────▼──────────┐
│   DynamoDB       │                      │  OpenSearch        │
│   - Products     │                      │  - Product Search  │
│   - Orders       │                      │  - Faceted Nav     │
│   - Carts        │                      │  - Analytics       │
└──────────────────┘                      └────────────────────┘

┌───────▼──────────┐
│  ElastiCache     │
│  (Redis)         │
│  - Sessions      │
│  - Hot Products  │
└──────────────────┘

Best Practices for Production E-commerce

1. Monitoring and Observability

Implement comprehensive monitoring using CloudWatch and X-Ray:

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import * as actions from 'aws-cdk-lib/aws-cloudwatch-actions';

// SNS topic for alerts
const alertTopic = new sns.Topic(this, 'AlertTopic');
alertTopic.addSubscription(new subscriptions.EmailSubscription('ops@forrict.nl'));

// Critical metric: API error rate
const apiErrorAlarm = new cloudwatch.Alarm(this, 'ApiErrorAlarm', {
  metric: api.metricServerError(),
  threshold: 10,
  evaluationPeriods: 2,
  datapointsToAlarm: 2,
  treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
  alarmDescription: 'API error rate is too high',
});
apiErrorAlarm.addAlarmAction(new actions.SnsAction(alertTopic));

// Cart abandonment tracking
const cartAbandonmentMetric = new cloudwatch.Metric({
  namespace: 'Ecommerce',
  metricName: 'CartAbandonment',
  statistic: 'Average',
});

// CloudFront cache hit ratio
const cacheHitAlarm = new cloudwatch.Alarm(this, 'CacheHitAlarm', {
  metric: distribution.metricCacheHitRate(),
  threshold: 85,
  comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
  evaluationPeriods: 3,
  alarmDescription: 'CloudFront cache hit rate is below 85%',
});

2. Cost Optimization

Implement cost controls for variable traffic:

  • Use Savings Plans for baseline ECS/Lambda capacity
  • Enable DynamoDB Auto Scaling to reduce costs during low traffic
  • Use S3 Intelligent-Tiering for product images
  • Implement CloudFront reserved capacity for predictable traffic
  • Right-size ElastiCache instances based on actual memory usage

3. Security Hardening

Essential security measures:

  • Enable AWS WAF on CloudFront to block common attacks
  • Implement AWS Shield Standard (free) or Advanced for DDoS protection
  • Use AWS Secrets Manager for API keys and database credentials
  • Enable VPC Flow Logs for network monitoring
  • Implement GuardDuty for threat detection
  • Enable MFA for all AWS accounts
  • Use IAM roles with least-privilege permissions

4. Disaster Recovery

Implement multi-region failover:

// DynamoDB Global Tables for multi-region replication
const globalTable = new dynamodb.Table(this, 'GlobalProductTable', {
  partitionKey: { name: 'productId', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
  replicationRegions: ['eu-central-1', 'us-east-1'],
  pointInTimeRecovery: true,
  stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
});

Conclusion

Building a scalable e-commerce platform on AWS requires careful architecture decisions and deep understanding of traffic patterns, especially in the Dutch market with its unique shopping behaviors. By leveraging CloudFront for global content delivery, DynamoDB for flexible product catalogs, Auto Scaling for traffic spikes, and managed services for payments and personalization, you can build a platform that rivals major Dutch retailers like Bol.com and Coolblue.

The key advantages of AWS for e-commerce are:

Elasticity: Automatically scale from hundreds to millions of users without manual intervention or over-provisioning.

Global Reach: Deliver content with low latency to customers across Europe and worldwide using CloudFront’s edge network.

Managed Services: Focus on business logic rather than infrastructure management with services like DynamoDB, Lambda, and Personalize.

Security and Compliance: Built-in tools for PCI-DSS compliance, GDPR data protection, and DDoS mitigation.

Cost Efficiency: Pay only for what you use, with automatic scaling down during low-traffic periods.

At Forrict, we help Dutch e-commerce companies architect, build, and optimize their AWS infrastructure. Whether you’re launching a new online store or scaling an existing platform, our team brings deep expertise in AWS services and e-commerce best practices. Contact us to discuss how we can help you build a world-class e-commerce platform on AWS.

F

Forrict Team

AWS expert and consultant at Forrict, specializing in cloud architecture and AWS best practices for Dutch businesses.

Tags

AWS E-commerce Retail DynamoDB CloudFront Auto Scaling Netherlands Architecture

Related Articles

Ready to Transform Your AWS Infrastructure?

Let's discuss how we can help optimize your cloud journey