AWS CDK Link-Verkürzer Teil 2: Kernfunktionalität & API-Entwicklung

Aufbau der Redirect-Engine, Analytics-Sammlung und API Gateway-Konfiguration. Echte Performance-Optimierungen und Debugging-Strategien für millionenfache tägliche Weiterleitungen.

AWS CDK Link-Verkürzer Teil 2: Kernfunktionalität & API-Entwicklung#

Da waren wir also, mitten in einer Board-Präsentation vor unseren Investoren, zeigten unser schickes neues Marketing-Kampagnen-Tracking-System. Ich klickte auf einen unserer verkürzten Links... und wartete. Und wartete. Die Weiterleitung dauerte 3 Sekunden. Der Raum wurde still. Der CEO warf mir diesen Blick zu.

Da lernte ich, dass der Bau eines Link-Verkürzer nicht nur darum geht, kurze Codes zu generieren—es geht darum, eine Weiterleitungs-Engine zu bauen, die mit Skalierung elegant umgehen kann. Nach dieser etwas peinlichen Demo (wir bekamen trotzdem die Finanzierung) bauten wir unser Weiterleitungssystem mit ordentlichem Caching, Analytics und Error Handling neu auf.

In Teil 1 haben wir das Fundament gelegt. Jetzt bauen wir die eigentliche Business-Logik, die das Ding zum Fliegen bringt.

Die Weiterleitungs-Engine: Wo Geschwindigkeit zählt#

Der Redirect Handler ist das Herz deines Link-Verkürzer. Jede Millisekunde zählt, weil Benutzer sofortige Weiterleitungen erwarten. Hier ist unsere produktionserprobte Implementierung:

TypeScript
// lambda/redirect.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
import { unmarshall } from '@aws-sdk/util-dynamodb';

const dynamodb = new DynamoDBClient({
  region: process.env.AWS_REGION,
  // Connection Pooling für bessere Performance
  maxAttempts: 3,
  requestHandler: {
    connectionTimeout: 1000,
    requestTimeout: 2000,
  }
});

interface AnalyticsEvent {
  shortCode: string;
  timestamp: number;
  userAgent?: string;
  referer?: string;
  ip?: string;
  country?: string;
}

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  const startTime = Date.now();
  const shortCode = event.pathParameters?.shortCode;
  
  if (!shortCode) {
    return createErrorResponse(400, 'Short Code ist erforderlich');
  }

  try {
    // URL aus DynamoDB holen
    const result = await dynamodb.send(new GetItemCommand({
      TableName: process.env.LINKS_TABLE_NAME!,
      Key: { shortCode: { S: shortCode } },
      ProjectionExpression: 'originalUrl, expiresAt, clickCount',
    }));

    if (!result.Item) {
      // 404s für Analytics tracken
      await trackAnalytics({
        shortCode,
        timestamp: Date.now(),
        userAgent: event.headers['User-Agent'],
        referer: event.headers['Referer'],
        ip: event.requestContext.identity?.sourceIp,
      }, 'NOT_FOUND');
      
      return createErrorResponse(404, 'Link nicht gefunden');
    }

    const item = unmarshall(result.Item);
    
    // Ablauf prüfen
    if (item.expiresAt && Date.now() > item.expiresAt) {
      return createErrorResponse(410, 'Link ist abgelaufen');
    }

    // Analytics asynchron tracken (blockiere nicht die Weiterleitung)
    trackAnalytics({
      shortCode,
      timestamp: Date.now(),
      userAgent: event.headers['User-Agent'],
      referer: event.headers['Referer'],
      ip: event.requestContext.identity?.sourceIp,
    }, 'SUCCESS').catch(error => {
      console.error('Analytics Tracking fehlgeschlagen:', error);
      // Lass die Weiterleitung nicht fehlschlagen, wenn Analytics fehlschlagen
    });

    // Performance Metriken loggen
    const responseTime = Date.now() - startTime;
    console.log(`Weiterleitung in ${responseTime}ms verarbeitet für ${shortCode}`);

    return {
      statusCode: 301,
      headers: {
        Location: item.originalUrl,
        'Cache-Control': 'public, max-age=300', // 5 Minuten
        'X-Response-Time': `${responseTime}ms`,
      },
      body: '',
    };

  } catch (error) {
    console.error('Weiterleitungsfehler:', error);
    
    return createErrorResponse(500, 'Interner Serverfehler');
  }
};

Analytics: Die Business Intelligence Schicht#

Analytics machte unseren Link-Verkürzer über die reine Bequemlichkeit hinaus wertvoll. So sammeln und speichern wir Click-Daten:

TypeScript
// lambda/analytics.ts
import { DynamoDBClient, PutItemCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
import { marshall } from '@aws-sdk/util-dynamodb';

const dynamodb = new DynamoDBClient({ region: process.env.AWS_REGION });

async function trackAnalytics(
  event: AnalyticsEvent, 
  eventType: 'SUCCESS' | 'NOT_FOUND' = 'SUCCESS'
): Promise<void> {
  const timestamp = Date.now();
  const analyticsItem = {
    shortCode: event.shortCode,
    timestamp,
    eventType,
    userAgent: event.userAgent || 'unknown',
    referer: event.referer || 'direct',
    ip: hashIP(event.ip || ''), // Privacy-first Ansatz
    country: await getCountryFromIP(event.ip),
    // Nach Stunden partitionieren für effiziente Abfragen
    hourPartition: `${event.shortCode}#${Math.floor(timestamp / (1000 * 60 * 60))}`,
  };

  // In Analytics-Tabelle speichern
  await dynamodb.send(new PutItemCommand({
    TableName: process.env.ANALYTICS_TABLE_NAME!,
    Item: marshall(analyticsItem),
  }));

  // Click-Anzahl auf Haupteintrag aktualisieren (nur für erfolgreiche Clicks)
  if (eventType === 'SUCCESS') {
    await dynamodb.send(new UpdateItemCommand({
      TableName: process.env.LINKS_TABLE_NAME!,
      Key: { shortCode: { S: event.shortCode } },
      UpdateExpression: 'ADD clickCount :inc SET lastClickAt = :timestamp',
      ExpressionAttributeValues: {
        ':inc': { N: '1' },
        ':timestamp': { N: timestamp.toString() },
      },
    }));
  }
}

API Gateway: Die Eingangstür#

Hier ist unsere CDK-Konfiguration, die Millionen von Anfragen handhabt, ohne zu brechen:

TypeScript
// lib/api-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';

export class ApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: ApiStackProps) {
    super(scope, id, props);

    // API Gateway mit Custom Domain
    const api = new apigateway.RestApi(this, 'LinkShortenerApi', {
      restApiName: 'Link Verkürzer Service',
      description: 'Produktions Link-Verkürzer API',
      
      // Performance-Optimierungen
      minimumCompressionSize: 1024,
      binaryMediaTypes: ['*/*'],
      
      // CORS-Konfiguration
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: ['GET', 'POST', 'OPTIONS'],
        allowHeaders: [
          'Content-Type',
          'X-Amz-Date',
          'Authorization',
          'X-Api-Key',
          'X-Amz-Security-Token',
        ],
        maxAge: cdk.Duration.hours(1),
      },
    });

    // Weiterleitungsroute hinzufügen: GET /{shortCode}
    const redirectIntegration = new apigateway.LambdaIntegration(props.redirectHandler, {
      proxy: true,
      allowTestInvoke: false, // Test Invoke für Performance deaktivieren
    });

    api.root.addResource('{shortCode}').addMethod('GET', redirectIntegration);
  }
}

Performance-Lektionen aus der Produktion#

Nach dem Bearbeiten von 50M+ Weiterleitungen sind hier die Performance-Pattern, die wirklich wichtig sind:

1. Connection Pooling spart 50ms pro Anfrage#

Die DynamoDB-Client-Konfiguration oben beinhaltet Connection Pooling. Ohne es erstellt jeder Lambda Cold Start neue Verbindungen und fügt 50-100ms Latenz hinzu.

2. Asynchrone Analytics blockieren Benutzer nicht#

Anfangs trackten wir Analytics synchron. Schlechte Idee. Benutzer interessieren sich nicht dafür, wenn Analytics fehlschlagen, aber sie interessieren sich definitiv, wenn Weiterleitungen langsam sind.

3. DynamoDB-Projektionen sind wichtig#

Die Verwendung von ProjectionExpression in unseren GetItem-Aufrufen reduzierte die Antwortgrößen um 60%.

In Teil 3 werden wir die Sicherheitsfeatures hinzufügen, die deinen Service davor bewahren, ein Spam-Vektor zu werden.

AWS CDK Link-Verkürzer: Von Null auf Produktion

Eine umfassende 5-teilige Serie über den Aufbau eines produktionsreifen Link-Verkürzungsdienstes mit AWS CDK, Node.js Lambda und DynamoDB. Mit echten Produktionsgeschichten, Performance-Optimierung und Kostenmanagement.

Fortschritt2/5 Beiträge abgeschlossen
Loading...

Kommentare (0)

An der Unterhaltung teilnehmen

Melde dich an, um deine Gedanken zu teilen und mit der Community zu interagieren

Noch keine Kommentare

Sei der erste, der deine Gedanken zu diesem Beitrag teilt!

Related Posts