Advanced Design Patterns - 2/2

The $50 Million Architecture Rewrite That Could Have Been Avoided

Picture this absolute disaster: A fintech unicorn with 50 million users hits the scaling wall harder than a brick hits concrete. Their monolithic trading platform—processing billions in transactions daily—starts showing cracks that threaten to bring down the entire company.

The symptoms were catastrophic:

  • Read queries killing write performance: Complex reporting queries were locking critical tables for minutes, blocking urgent trade executions
  • Audit trail nightmare: Reconstructing user account histories required joining 47 tables and took 6+ hours per audit request
  • Deployment hell: Rolling out a small UI change required deploying the entire monolith, risking billion-dollar trading algorithms
  • Team paralysis: 200 developers constantly stepping on each other’s toes, with feature branches that couldn’t merge for months
  • Data inconsistency: Customer balances showing different values in different parts of the application
  • Performance death spiral: Response times degrading exponentially as the single database struggled with mixed read/write workloads

Here’s what led to their $50 million emergency architecture rewrite:

  • No separation between reads and writes: Analytical queries competed with real-time trading operations
  • No event history: Understanding how accounts reached their current state was impossible without forensic database archaeology
  • No business domain boundaries: Payment logic was mixed with user management, trading algorithms, and reporting—all in one massive codebase
  • No workflow coordination: Multi-step business processes were scattered across dozens of services with no orchestration
  • No architectural principles: 5 years of “rapid development” with zero thought given to long-term maintainability

By the time they brought in a team of enterprise architects:

  • $50 million spent on a complete platform rewrite over 18 months
  • 40% of engineering team quit during the migration chaos
  • 8 months of feature development stopped while they untangled the monolith
  • 3 critical customer data corruption incidents during the migration
  • 12 months of performance issues as they learned their new architecture in production
  • Complete loss of market confidence when competitors shipped features faster during their architectural hell

The brutal truth? Every single one of these problems could have been solved with proper implementation of advanced architectural patterns that enterprise-grade systems use by default.

The Uncomfortable Truth About Enterprise Architecture

Here’s what separates systems that scale to millions of users from those that collapse under their own complexity: Advanced architectural patterns aren’t just about organizing code—they’re about modeling your business domain in a way that matches how the real world actually works, while keeping your system flexible enough to handle constant change.

Most developers approach enterprise architecture like this:

  1. Start with a monolith because “microservices are complex”
  2. Add features by jamming more code into existing modules
  3. Use shared databases because “data consistency is important”
  4. Build complex business workflows with scattered service calls and manual error handling
  5. Discover that their architecture actively fights against business requirements

But systems that handle real enterprise complexity work differently:

  1. Model the business domain explicitly using Domain-Driven Design principles
  2. Separate command and query concerns so reads and writes can scale independently
  3. Treat events as first-class citizens to maintain complete audit trails and enable complex workflows
  4. Coordinate business processes with explicit saga patterns that handle partial failures gracefully
  5. Build in layers with clear boundaries that prevent business logic from leaking into infrastructure concerns

The difference isn’t just code organization—it’s the difference between systems that grow more powerful as requirements become more complex and systems that become exponentially more brittle with each new feature.

Ready to build applications like Amazon’s order processing engine instead of that monolith that makes everyone afraid to deploy on Fridays? Let’s dive into the architectural patterns that power enterprise-grade systems.


Command Query Responsibility Segregation (CQRS): Separating Reads from Writes

The Problem: Mixed Read/Write Workloads Creating Performance Hell

// The monolithic nightmare that kills performance at scale
class UserAccountService {
  async getUserDashboard(userId: string) {
    // Complex read query that locks tables - RED FLAG #1
    const user = await this.db.query(
      `
      SELECT u.*, p.*, 
             COUNT(t.id) as total_transactions,
             SUM(t.amount) as total_volume,
             AVG(t.amount) as avg_transaction,
             MAX(t.created_at) as last_transaction,
             COUNT(CASE WHEN t.created_at > NOW() - INTERVAL '30 days' THEN 1 END) as monthly_transactions
      FROM users u
      LEFT JOIN profiles p ON u.id = p.user_id
      LEFT JOIN transactions t ON u.id = t.user_id
      WHERE u.id = ? AND u.deleted_at IS NULL
      GROUP BY u.id, p.id
    `,
      [userId]
    );

    // More complex aggregations that compete with writes - RED FLAG #2
    const recentActivity = await this.db.query(
      `
      SELECT t.*, p.name as product_name, c.name as category_name
      FROM transactions t
      JOIN products p ON t.product_id = p.id
      JOIN categories c ON p.category_id = c.id
      WHERE t.user_id = ?
      ORDER BY t.created_at DESC
      LIMIT 50
    `,
      [userId]
    );

    // Analytics queries blocking critical operations - RED FLAG #3
    const monthlyStats = await this.db.query(
      `
      SELECT DATE_TRUNC('month', created_at) as month,
             COUNT(*) as transaction_count,
             SUM(amount) as total_amount,
             AVG(amount) as avg_amount
      FROM transactions
      WHERE user_id = ? AND created_at > NOW() - INTERVAL '12 months'
      GROUP BY DATE_TRUNC('month', created_at)
      ORDER BY month
    `,
      [userId]
    );

    return { user, recentActivity, monthlyStats };
  }

  async createTransaction(transactionData: any) {
    // Critical write operation competing with dashboard queries - RED FLAG #4
    // This might wait minutes if dashboard query has table locks

    const transaction = await this.db.transaction(async (trx) => {
      // Update user balance
      await trx.query("UPDATE users SET balance = balance + ? WHERE id = ?", [
        transactionData.amount,
        transactionData.userId,
      ]);

      // Create transaction record
      const transaction = await trx.query(
        "INSERT INTO transactions (user_id, amount, type, created_at) VALUES (?, ?, ?, NOW()) RETURNING *",
        [transactionData.userId, transactionData.amount, transactionData.type]
      );

      // Update analytics tables (blocking critical business logic) - RED FLAG #5
      await trx.query(
        `
        INSERT INTO monthly_stats (user_id, month, transaction_count, total_amount)
        VALUES (?, DATE_TRUNC('month', NOW()), 1, ?)
        ON CONFLICT (user_id, month) 
        DO UPDATE SET 
          transaction_count = monthly_stats.transaction_count + 1,
          total_amount = monthly_stats.total_amount + ?
      `,
        [transactionData.userId, transactionData.amount, transactionData.amount]
      );

      return transaction;
    });

    return transaction;
  }
}

The Solution: CQRS with Separate Read and Write Models

// Domain model focused on business operations (Write side)
export class Account {
  private events: DomainEvent[] = [];

  constructor(
    public readonly id: string,
    public readonly ownerId: string,
    private balance: number,
    private status: AccountStatus,
    private readonly createdAt: Date
  ) {}

  // Business logic focused on commands
  deposit(amount: number, description: string): void {
    if (amount <= 0) {
      throw new DomainError("Deposit amount must be positive");
    }

    if (this.status !== AccountStatus.Active) {
      throw new DomainError("Cannot deposit to inactive account");
    }

    this.balance += amount;

    this.addEvent(
      new MoneyDepositedEvent(
        this.id,
        this.ownerId,
        amount,
        this.balance,
        description
      )
    );
  }

  withdraw(amount: number, description: string): void {
    if (amount <= 0) {
      throw new DomainError("Withdrawal amount must be positive");
    }

    if (this.status !== AccountStatus.Active) {
      throw new DomainError("Cannot withdraw from inactive account");
    }

    if (this.balance < amount) {
      throw new DomainError("Insufficient funds");
    }

    this.balance -= amount;

    this.addEvent(
      new MoneyWithdrawnEvent(
        this.id,
        this.ownerId,
        amount,
        this.balance,
        description
      )
    );
  }

  freeze(reason: string): void {
    if (this.status === AccountStatus.Frozen) {
      return; // Already frozen
    }

    this.status = AccountStatus.Frozen;

    this.addEvent(new AccountFrozenEvent(this.id, this.ownerId, reason));
  }

  // Domain event management
  getUncommittedEvents(): DomainEvent[] {
    return [...this.events];
  }

  clearEvents(): void {
    this.events = [];
  }

  private addEvent(event: DomainEvent): void {
    this.events.push(event);
  }

  // Getters for business logic
  getCurrentBalance(): number {
    return this.balance;
  }

  isActive(): boolean {
    return this.status === AccountStatus.Active;
  }

  canWithdraw(amount: number): boolean {
    return this.isActive() && this.balance >= amount;
  }
}

// Command side - focused on business operations
export interface ICommand {
  readonly commandId: string;
  readonly userId: string;
  readonly timestamp: Date;
}

export class DepositMoneyCommand implements ICommand {
  readonly commandId = uuidv4();
  readonly timestamp = new Date();

  constructor(
    public readonly userId: string,
    public readonly accountId: string,
    public readonly amount: number,
    public readonly description: string
  ) {}
}

export class WithdrawMoneyCommand implements ICommand {
  readonly commandId = uuidv4();
  readonly timestamp = new Date();

  constructor(
    public readonly userId: string,
    public readonly accountId: string,
    public readonly amount: number,
    public readonly description: string
  ) {}
}

// Command handlers - pure business logic
export class AccountCommandHandler {
  constructor(
    private accountRepository: IAccountRepository,
    private eventStore: IEventStore,
    private eventBus: IEventBus
  ) {}

  async handle(command: DepositMoneyCommand): Promise<void> {
    // Load aggregate from repository
    const account = await this.accountRepository.findById(command.accountId);
    if (!account) {
      throw new DomainError("Account not found");
    }

    // Execute business logic
    account.deposit(command.amount, command.description);

    // Save changes
    await this.accountRepository.save(account);

    // Publish domain events
    const events = account.getUncommittedEvents();
    for (const event of events) {
      await this.eventBus.publish(event);
    }

    account.clearEvents();
  }

  async handle(command: WithdrawMoneyCommand): Promise<void> {
    const account = await this.accountRepository.findById(command.accountId);
    if (!account) {
      throw new DomainError("Account not found");
    }

    account.withdraw(command.amount, command.description);

    await this.accountRepository.save(account);

    const events = account.getUncommittedEvents();
    for (const event of events) {
      await this.eventBus.publish(event);
    }

    account.clearEvents();
  }
}

// Read side - optimized for queries
export class AccountDashboardViewModel {
  constructor(
    public readonly accountId: string,
    public readonly ownerId: string,
    public readonly ownerName: string,
    public readonly currentBalance: number,
    public readonly status: string,
    public readonly totalTransactions: number,
    public readonly totalDeposits: number,
    public readonly totalWithdrawals: number,
    public readonly avgTransactionAmount: number,
    public readonly lastTransactionDate: Date | null,
    public readonly monthlyStats: MonthlyStats[],
    public readonly recentActivity: TransactionSummary[]
  ) {}
}

export class TransactionSummary {
  constructor(
    public readonly id: string,
    public readonly type: "deposit" | "withdrawal",
    public readonly amount: number,
    public readonly description: string,
    public readonly timestamp: Date,
    public readonly balanceAfter: number
  ) {}
}

export class MonthlyStats {
  constructor(
    public readonly month: string,
    public readonly transactionCount: number,
    public readonly totalAmount: number,
    public readonly averageAmount: number
  ) {}
}

// Query handlers - optimized for reads
export class AccountQueryHandler {
  constructor(
    private readDatabase: IReadDatabase // Separate read database
  ) {}

  async getDashboard(
    accountId: string
  ): Promise<AccountDashboardViewModel | null> {
    // Single optimized query from denormalized read model
    const result = await this.readDatabase.query(
      `
      SELECT 
        a.account_id,
        a.owner_id,
        a.owner_name,
        a.current_balance,
        a.status,
        a.total_transactions,
        a.total_deposits,
        a.total_withdrawals,
        a.avg_transaction_amount,
        a.last_transaction_date
      FROM account_dashboard_view a
      WHERE a.account_id = ?
    `,
      [accountId]
    );

    if (!result.rows[0]) {
      return null;
    }

    const account = result.rows[0];

    // Get monthly stats from pre-aggregated table
    const monthlyStats = await this.readDatabase.query(
      `
      SELECT month, transaction_count, total_amount, average_amount
      FROM monthly_account_stats
      WHERE account_id = ?
      ORDER BY month DESC
      LIMIT 12
    `,
      [accountId]
    );

    // Get recent activity from optimized table
    const recentActivity = await this.readDatabase.query(
      `
      SELECT id, type, amount, description, timestamp, balance_after
      FROM transaction_summaries
      WHERE account_id = ?
      ORDER BY timestamp DESC
      LIMIT 20
    `,
      [accountId]
    );

    return new AccountDashboardViewModel(
      account.account_id,
      account.owner_id,
      account.owner_name,
      account.current_balance,
      account.status,
      account.total_transactions,
      account.total_deposits,
      account.total_withdrawals,
      account.avg_transaction_amount,
      account.last_transaction_date,
      monthlyStats.rows.map(
        (row) =>
          new MonthlyStats(
            row.month,
            row.transaction_count,
            row.total_amount,
            row.average_amount
          )
      ),
      recentActivity.rows.map(
        (row) =>
          new TransactionSummary(
            row.id,
            row.type,
            row.amount,
            row.description,
            row.timestamp,
            row.balance_after
          )
      )
    );
  }

  async getAccountHistory(
    accountId: string,
    fromDate: Date,
    toDate: Date
  ): Promise<TransactionSummary[]> {
    // Optimized query for historical data
    const result = await this.readDatabase.query(
      `
      SELECT id, type, amount, description, timestamp, balance_after
      FROM transaction_summaries
      WHERE account_id = ? AND timestamp BETWEEN ? AND ?
      ORDER BY timestamp DESC
    `,
      [accountId, fromDate, toDate]
    );

    return result.rows.map(
      (row) =>
        new TransactionSummary(
          row.id,
          row.type,
          row.amount,
          row.description,
          row.timestamp,
          row.balance_after
        )
    );
  }
}

// Read model projections - update read side when events occur
export class AccountDashboardProjection {
  constructor(private readDatabase: IReadDatabase) {}

  async handle(event: MoneyDepositedEvent): Promise<void> {
    // Update denormalized dashboard view
    await this.readDatabase.query(
      `
      UPDATE account_dashboard_view 
      SET 
        current_balance = ?,
        total_transactions = total_transactions + 1,
        total_deposits = total_deposits + ?,
        avg_transaction_amount = (total_deposits + total_withdrawals) / total_transactions,
        last_transaction_date = ?
      WHERE account_id = ?
    `,
      [event.balanceAfter, event.amount, event.timestamp, event.accountId]
    );

    // Insert transaction summary for quick queries
    await this.readDatabase.query(
      `
      INSERT INTO transaction_summaries 
      (id, account_id, type, amount, description, timestamp, balance_after)
      VALUES (?, ?, 'deposit', ?, ?, ?, ?)
    `,
      [
        uuidv4(),
        event.accountId,
        event.amount,
        event.description,
        event.timestamp,
        event.balanceAfter,
      ]
    );

    // Update monthly stats
    await this.readDatabase.query(
      `
      INSERT INTO monthly_account_stats 
      (account_id, month, transaction_count, total_amount, average_amount)
      VALUES (?, DATE_TRUNC('month', ?), 1, ?, ?)
      ON CONFLICT (account_id, month)
      DO UPDATE SET
        transaction_count = monthly_account_stats.transaction_count + 1,
        total_amount = monthly_account_stats.total_amount + ?,
        average_amount = monthly_account_stats.total_amount / monthly_account_stats.transaction_count
    `,
      [
        event.accountId,
        event.timestamp,
        event.amount,
        event.amount,
        event.amount,
      ]
    );
  }

  async handle(event: MoneyWithdrawnEvent): Promise<void> {
    // Similar logic for withdrawals
    await this.readDatabase.query(
      `
      UPDATE account_dashboard_view 
      SET 
        current_balance = ?,
        total_transactions = total_transactions + 1,
        total_withdrawals = total_withdrawals + ?,
        avg_transaction_amount = (total_deposits + total_withdrawals) / total_transactions,
        last_transaction_date = ?
      WHERE account_id = ?
    `,
      [event.balanceAfter, event.amount, event.timestamp, event.accountId]
    );

    await this.readDatabase.query(
      `
      INSERT INTO transaction_summaries 
      (id, account_id, type, amount, description, timestamp, balance_after)
      VALUES (?, ?, 'withdrawal', ?, ?, ?, ?)
    `,
      [
        uuidv4(),
        event.accountId,
        event.amount,
        event.description,
        event.timestamp,
        event.balanceAfter,
      ]
    );
  }
}

// Clean API layer
export class AccountController {
  constructor(
    private commandHandler: AccountCommandHandler,
    private queryHandler: AccountQueryHandler
  ) {}

  // Command endpoint
  async deposit(req: Request, res: Response): Promise<void> {
    try {
      const { accountId, amount, description } = req.body;
      const userId = req.user.id;

      const command = new DepositMoneyCommand(
        userId,
        accountId,
        amount,
        description
      );
      await this.commandHandler.handle(command);

      res.status(202).json({
        message: "Deposit initiated",
        commandId: command.commandId,
      });
    } catch (error) {
      if (error instanceof DomainError) {
        res.status(400).json({ error: error.message });
      } else {
        res.status(500).json({ error: "Internal server error" });
      }
    }
  }

  // Query endpoint
  async getDashboard(req: Request, res: Response): Promise<void> {
    try {
      const accountId = req.params.accountId;
      const dashboard = await this.queryHandler.getDashboard(accountId);

      if (!dashboard) {
        res.status(404).json({ error: "Account not found" });
        return;
      }

      res.json(dashboard);
    } catch (error) {
      res.status(500).json({ error: "Internal server error" });
    }
  }
}

Event Sourcing: The Complete Audit Trail That Powers Business Intelligence

The Problem: Losing Business Critical Information

// The traditional approach that loses valuable business data
class TraditionalUserService {
  async updateUserProfile(userId: string, updates: any) {
    // Destructive update - we lose the history forever - RED FLAG #1
    await this.db.query(
      `
      UPDATE users 
      SET email = ?, name = ?, phone = ?, updated_at = NOW()
      WHERE id = ?
    `,
      [updates.email, updates.name, updates.phone, userId]
    );

    // No way to answer business questions like:
    // - When did the user change their email?
    // - What was their previous phone number?
    // - How many times have they updated their profile?
    // - Did they change their email right before a suspicious transaction?
    // - What was the sequence of changes that led to their current state?
  }

  async processPayment(paymentData: any) {
    // Update account balance - losing the story of how we got here
    await this.db.query(
      `
      UPDATE accounts 
      SET balance = balance + ?
      WHERE user_id = ?
    `,
      [paymentData.amount, paymentData.userId]
    );

    // Insert transaction record - but lose rich context
    await this.db.query(
      `
      INSERT INTO transactions (user_id, amount, type, created_at)
      VALUES (?, ?, ?, NOW())
    `,
      [paymentData.userId, paymentData.amount, "payment"]
    );

    // Questions we can't answer:
    // - What was the exact sequence of operations?
    // - If there was a conflict, which update won?
    // - What was the user's balance at any point in time?
    // - Can we replay this sequence to debug issues?
  }
}

The Solution: Event Sourcing for Complete Business History

// Domain events capture business facts
export abstract class DomainEvent {
  public readonly eventId = uuidv4();
  public readonly timestamp = new Date();
  public readonly version = 1;

  constructor(
    public readonly aggregateId: string,
    public readonly aggregateType: string
  ) {}

  abstract getEventType(): string;
}

export class UserRegisteredEvent extends DomainEvent {
  constructor(
    aggregateId: string,
    public readonly email: string,
    public readonly name: string,
    public readonly registrationSource: string,
    public readonly initialProfile: UserProfile
  ) {
    super(aggregateId, "User");
  }

  getEventType(): string {
    return "UserRegistered";
  }
}

export class UserEmailChangedEvent extends DomainEvent {
  constructor(
    aggregateId: string,
    public readonly previousEmail: string,
    public readonly newEmail: string,
    public readonly changeReason: string,
    public readonly verificationRequired: boolean
  ) {
    super(aggregateId, "User");
  }

  getEventType(): string {
    return "UserEmailChanged";
  }
}

export class UserProfileUpdatedEvent extends DomainEvent {
  constructor(
    aggregateId: string,
    public readonly fieldChanges: ProfileFieldChange[],
    public readonly updatedBy: string,
    public readonly updateReason?: string
  ) {
    super(aggregateId, "User");
  }

  getEventType(): string {
    return "UserProfileUpdated";
  }
}

export class MoneyDepositedEvent extends DomainEvent {
  constructor(
    aggregateId: string,
    public readonly accountId: string,
    public readonly amount: number,
    public readonly balanceAfter: number,
    public readonly description: string,
    public readonly paymentMethod: string,
    public readonly externalTransactionId?: string
  ) {
    super(aggregateId, "Account");
  }

  getEventType(): string {
    return "MoneyDeposited";
  }
}

export class PaymentProcessedEvent extends DomainEvent {
  constructor(
    aggregateId: string,
    public readonly paymentId: string,
    public readonly amount: number,
    public readonly currency: string,
    public readonly merchant: string,
    public readonly paymentMethod: string,
    public readonly processingFee: number,
    public readonly riskScore: number
  ) {
    super(aggregateId, "Payment");
  }

  getEventType(): string {
    return "PaymentProcessed";
  }
}

// Event store interface
export interface IEventStore {
  saveEvents(
    aggregateId: string,
    events: DomainEvent[],
    expectedVersion: number
  ): Promise<void>;
  getEvents(aggregateId: string, fromVersion?: number): Promise<DomainEvent[]>;
  getAllEvents(
    fromPosition?: number,
    maxCount?: number
  ): Promise<DomainEvent[]>;
  getEventsByType(
    eventType: string,
    fromTimestamp?: Date
  ): Promise<DomainEvent[]>;
}

// Event store implementation
export class PostgresEventStore implements IEventStore {
  constructor(private db: Database) {}

  async saveEvents(
    aggregateId: string,
    events: DomainEvent[],
    expectedVersion: number
  ): Promise<void> {
    if (events.length === 0) return;

    const transaction = await this.db.beginTransaction();

    try {
      // Optimistic concurrency control
      const currentVersion = await this.getCurrentVersion(
        aggregateId,
        transaction
      );
      if (currentVersion !== expectedVersion) {
        throw new ConcurrencyError(
          `Expected version ${expectedVersion}, but current version is ${currentVersion}`
        );
      }

      // Save events
      for (const event of events) {
        await transaction.query(
          `
          INSERT INTO event_store 
          (event_id, aggregate_id, aggregate_type, event_type, event_data, version, timestamp)
          VALUES (?, ?, ?, ?, ?, ?, ?)
        `,
          [
            event.eventId,
            event.aggregateId,
            event.aggregateType,
            event.getEventType(),
            JSON.stringify(event),
            expectedVersion + 1,
            event.timestamp,
          ]
        );
        expectedVersion++;
      }

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  async getEvents(
    aggregateId: string,
    fromVersion: number = 0
  ): Promise<DomainEvent[]> {
    const result = await this.db.query(
      `
      SELECT event_data, event_type
      FROM event_store
      WHERE aggregate_id = ? AND version > ?
      ORDER BY version ASC
    `,
      [aggregateId, fromVersion]
    );

    return result.rows.map((row) =>
      this.deserializeEvent(row.event_data, row.event_type)
    );
  }

  async getAllEvents(
    fromPosition: number = 0,
    maxCount: number = 1000
  ): Promise<DomainEvent[]> {
    const result = await this.db.query(
      `
      SELECT event_data, event_type
      FROM event_store
      WHERE id > ?
      ORDER BY id ASC
      LIMIT ?
    `,
      [fromPosition, maxCount]
    );

    return result.rows.map((row) =>
      this.deserializeEvent(row.event_data, row.event_type)
    );
  }

  async getEventsByType(
    eventType: string,
    fromTimestamp?: Date
  ): Promise<DomainEvent[]> {
    let query = `
      SELECT event_data, event_type
      FROM event_store
      WHERE event_type = ?
    `;
    const params = [eventType];

    if (fromTimestamp) {
      query += ` AND timestamp >= ?`;
      params.push(fromTimestamp);
    }

    query += ` ORDER BY timestamp ASC`;

    const result = await this.db.query(query, params);
    return result.rows.map((row) =>
      this.deserializeEvent(row.event_data, row.event_type)
    );
  }

  private async getCurrentVersion(
    aggregateId: string,
    transaction: DatabaseTransaction
  ): Promise<number> {
    const result = await transaction.query(
      `
      SELECT COALESCE(MAX(version), 0) as version
      FROM event_store
      WHERE aggregate_id = ?
    `,
      [aggregateId]
    );

    return result.rows[0].version;
  }

  private deserializeEvent(eventData: string, eventType: string): DomainEvent {
    const data = JSON.parse(eventData);

    // Event factory based on type
    switch (eventType) {
      case "UserRegistered":
        return new UserRegisteredEvent(
          data.aggregateId,
          data.email,
          data.name,
          data.registrationSource,
          data.initialProfile
        );
      case "UserEmailChanged":
        return new UserEmailChangedEvent(
          data.aggregateId,
          data.previousEmail,
          data.newEmail,
          data.changeReason,
          data.verificationRequired
        );
      case "MoneyDeposited":
        return new MoneyDepositedEvent(
          data.aggregateId,
          data.accountId,
          data.amount,
          data.balanceAfter,
          data.description,
          data.paymentMethod,
          data.externalTransactionId
        );
      default:
        throw new Error(`Unknown event type: ${eventType}`);
    }
  }
}

// Aggregate that can be rebuilt from events
export class User {
  private version = 0;
  private uncommittedEvents: DomainEvent[] = [];

  constructor(
    public readonly id: string,
    private email: string = "",
    private name: string = "",
    private profile: UserProfile = new UserProfile(),
    private registrationDate?: Date,
    private isEmailVerified: boolean = false
  ) {}

  // Create new user (generates UserRegistered event)
  static register(
    email: string,
    name: string,
    registrationSource: string,
    initialProfile: UserProfile
  ): User {
    const user = new User(uuidv4());

    const event = new UserRegisteredEvent(
      user.id,
      email,
      name,
      registrationSource,
      initialProfile
    );

    user.apply(event);
    return user;
  }

  // Rebuild user from event history
  static fromHistory(events: DomainEvent[]): User {
    const user = new User(events[0]?.aggregateId || "");

    for (const event of events) {
      user.apply(event, false); // Don't add to uncommitted events
    }

    return user;
  }

  // Business methods that generate events
  changeEmail(
    newEmail: string,
    reason: string,
    requireVerification: boolean = true
  ): void {
    if (newEmail === this.email) {
      return; // No change needed
    }

    if (!this.isValidEmail(newEmail)) {
      throw new DomainError("Invalid email format");
    }

    const event = new UserEmailChangedEvent(
      this.id,
      this.email,
      newEmail,
      reason,
      requireVerification
    );

    this.apply(event);
  }

  updateProfile(
    changes: Partial<UserProfile>,
    updatedBy: string,
    reason?: string
  ): void {
    const fieldChanges: ProfileFieldChange[] = [];

    for (const [field, newValue] of Object.entries(changes)) {
      const currentValue = (this.profile as any)[field];
      if (currentValue !== newValue) {
        fieldChanges.push(
          new ProfileFieldChange(field, currentValue, newValue)
        );
      }
    }

    if (fieldChanges.length === 0) {
      return; // No changes
    }

    const event = new UserProfileUpdatedEvent(
      this.id,
      fieldChanges,
      updatedBy,
      reason
    );

    this.apply(event);
  }

  // Apply events to change state
  private apply(event: DomainEvent, isNew: boolean = true): void {
    switch (event.getEventType()) {
      case "UserRegistered":
        this.applyUserRegistered(event as UserRegisteredEvent);
        break;
      case "UserEmailChanged":
        this.applyUserEmailChanged(event as UserEmailChangedEvent);
        break;
      case "UserProfileUpdated":
        this.applyUserProfileUpdated(event as UserProfileUpdatedEvent);
        break;
    }

    this.version++;

    if (isNew) {
      this.uncommittedEvents.push(event);
    }
  }

  private applyUserRegistered(event: UserRegisteredEvent): void {
    this.email = event.email;
    this.name = event.name;
    this.profile = event.initialProfile;
    this.registrationDate = event.timestamp;
  }

  private applyUserEmailChanged(event: UserEmailChangedEvent): void {
    this.email = event.newEmail;
    this.isEmailVerified = !event.verificationRequired;
  }

  private applyUserProfileUpdated(event: UserProfileUpdatedEvent): void {
    for (const change of event.fieldChanges) {
      (this.profile as any)[change.fieldName] = change.newValue;
    }
  }

  // Repository interface methods
  getUncommittedEvents(): DomainEvent[] {
    return [...this.uncommittedEvents];
  }

  clearUncommittedEvents(): void {
    this.uncommittedEvents = [];
  }

  getVersion(): number {
    return this.version;
  }

  // Query methods
  getEmail(): string {
    return this.email;
  }

  getName(): string {
    return this.name;
  }

  getProfile(): UserProfile {
    return this.profile;
  }

  private isValidEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}

// Event-sourced repository
export class EventSourcedUserRepository {
  constructor(private eventStore: IEventStore) {}

  async findById(id: string): Promise<User | null> {
    const events = await this.eventStore.getEvents(id);

    if (events.length === 0) {
      return null;
    }

    return User.fromHistory(events);
  }

  async save(user: User): Promise<void> {
    const events = user.getUncommittedEvents();

    if (events.length === 0) {
      return; // No changes to save
    }

    await this.eventStore.saveEvents(
      user.id,
      events,
      user.getVersion() - events.length
    );
    user.clearUncommittedEvents();
  }
}

// Event sourcing enables powerful business intelligence
export class BusinessIntelligenceService {
  constructor(private eventStore: IEventStore) {}

  // Answer complex business questions using event history
  async getUserEmailChangeHistory(userId: string): Promise<EmailChange[]> {
    const events = await this.eventStore.getEvents(userId);
    const emailChanges: EmailChange[] = [];

    for (const event of events) {
      if (event instanceof UserEmailChangedEvent) {
        emailChanges.push(
          new EmailChange(
            event.previousEmail,
            event.newEmail,
            event.changeReason,
            event.timestamp
          )
        );
      }
    }

    return emailChanges;
  }

  async getAccountBalanceAtTime(
    accountId: string,
    pointInTime: Date
  ): Promise<number> {
    const events = await this.eventStore.getEvents(accountId);
    let balance = 0;

    for (const event of events) {
      if (event.timestamp > pointInTime) {
        break; // Stop at the point in time we're interested in
      }

      if (event instanceof MoneyDepositedEvent) {
        balance = event.balanceAfter; // Events contain the balance after the operation
      }
    }

    return balance;
  }

  async detectSuspiciousEmailChanges(
    timeWindow: number = 24
  ): Promise<SuspiciousActivity[]> {
    const suspicious: SuspiciousActivity[] = [];
    const cutoffTime = new Date(Date.now() - timeWindow * 60 * 60 * 1000);

    const emailChanges = await this.eventStore.getEventsByType(
      "UserEmailChanged",
      cutoffTime
    );

    for (const emailChange of emailChanges) {
      const event = emailChange as UserEmailChangedEvent;

      // Look for large transactions shortly after email change
      const paymentEvents = await this.eventStore.getEventsByType(
        "PaymentProcessed"
      );
      const recentPayments = paymentEvents.filter(
        (p) =>
          p.aggregateId === event.aggregateId &&
          p.timestamp > event.timestamp &&
          p.timestamp <= new Date(event.timestamp.getTime() + 60 * 60 * 1000) // Within 1 hour
      );

      const largePayments = recentPayments.filter((p) => {
        const payment = p as PaymentProcessedEvent;
        return payment.amount > 10000; // $10k+
      });

      if (largePayments.length > 0) {
        suspicious.push(
          new SuspiciousActivity(
            event.aggregateId,
            "large_payment_after_email_change",
            `User changed email then made ${largePayments.length} large payments within 1 hour`,
            event.timestamp
          )
        );
      }
    }

    return suspicious;
  }

  async replayEventsForDebugging(
    aggregateId: string
  ): Promise<StateTransition[]> {
    const events = await this.eventStore.getEvents(aggregateId);
    const transitions: StateTransition[] = [];

    let currentState: any = {};

    for (const event of events) {
      const previousState = { ...currentState };

      // Apply event to state (simplified)
      if (event instanceof UserEmailChangedEvent) {
        currentState.email = event.newEmail;
      } else if (event instanceof MoneyDepositedEvent) {
        currentState.balance = event.balanceAfter;
      }

      transitions.push(
        new StateTransition(
          event.getEventType(),
          event.timestamp,
          previousState,
          { ...currentState },
          event
        )
      );
    }

    return transitions;
  }
}

// Supporting types
export class UserProfile {
  constructor(
    public firstName: string = "",
    public lastName: string = "",
    public phone: string = "",
    public address: Address = new Address(),
    public preferences: UserPreferences = new UserPreferences()
  ) {}
}

export class Address {
  constructor(
    public street: string = "",
    public city: string = "",
    public state: string = "",
    public zipCode: string = "",
    public country: string = ""
  ) {}
}

export class UserPreferences {
  constructor(
    public emailNotifications: boolean = true,
    public smsNotifications: boolean = false,
    public language: string = "en",
    public timezone: string = "UTC"
  ) {}
}

export class ProfileFieldChange {
  constructor(
    public readonly fieldName: string,
    public readonly oldValue: any,
    public readonly newValue: any
  ) {}
}

export class EmailChange {
  constructor(
    public readonly previousEmail: string,
    public readonly newEmail: string,
    public readonly reason: string,
    public readonly timestamp: Date
  ) {}
}

export class SuspiciousActivity {
  constructor(
    public readonly userId: string,
    public readonly activityType: string,
    public readonly description: string,
    public readonly timestamp: Date
  ) {}
}

export class StateTransition {
  constructor(
    public readonly eventType: string,
    public readonly timestamp: Date,
    public readonly previousState: any,
    public readonly newState: any,
    public readonly event: DomainEvent
  ) {}
}

export class ConcurrencyError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ConcurrencyError";
  }
}

export class DomainError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "DomainError";
  }
}

enum AccountStatus {
  Active = "active",
  Frozen = "frozen",
  Closed = "closed",
}

Saga Pattern: Orchestrating Complex Business Workflows

The Problem: Distributed Transactions Without Coordination

// The distributed nightmare that creates data inconsistencies
class OrderProcessingService {
  async processOrder(orderData: any) {
    try {
      // Step 1: Reserve inventory
      const inventoryReservation = await this.inventoryService.reserveItems(
        orderData.items
      );

      try {
        // Step 2: Process payment
        const paymentResult = await this.paymentService.processPayment({
          customerId: orderData.customerId,
          amount: orderData.total,
        });

        try {
          // Step 3: Create order record
          const order = await this.orderService.createOrder(
            orderData,
            paymentResult.id
          );

          try {
            // Step 4: Update customer points
            await this.loyaltyService.addPoints(
              orderData.customerId,
              orderData.points
            );

            try {
              // Step 5: Send confirmation email
              await this.emailService.sendOrderConfirmation(order);

              return order;
            } catch (emailError) {
              // What do we rollback here? Everything? Just some things?
              console.error("Email failed, but order is already created...");
              throw emailError;
            }
          } catch (loyaltyError) {
            // Need to rollback order, payment, inventory...
            console.error("Loyalty update failed, rolling back order...");
            throw loyaltyError;
          }
        } catch (orderError) {
          // Need to rollback payment and inventory...
          console.error("Order creation failed, rolling back payment...");
          throw orderError;
        }
      } catch (paymentError) {
        // Need to rollback inventory...
        console.error("Payment failed, rolling back inventory...");
        throw paymentError;
      }
    } catch (inventoryError) {
      // First step failed, nothing to rollback
      throw inventoryError;
    }

    // This nested try-catch hell is unmaintainable and error-prone
    // What if a compensation action also fails?
    // How do we handle partial failures?
    // How do we retry individual steps?
    // How do we monitor long-running processes?
  }
}

The Solution: Saga Pattern for Distributed Workflow Orchestration

// Saga interface for coordinating distributed transactions
export interface ISaga {
  readonly sagaId: string;
  readonly sagaType: string;
  execute(): Promise<void>;
  compensate(): Promise<void>;
  getState(): SagaState;
}

export enum SagaState {
  NotStarted = "not_started",
  Running = "running",
  Completed = "completed",
  Compensating = "compensating",
  Failed = "failed",
  Compensated = "compensated",
}

export interface SagaStep {
  name: string;
  execute(): Promise<StepResult>;
  compensate(): Promise<void>;
  canCompensate(): boolean;
}

export class StepResult {
  constructor(
    public success: boolean,
    public data?: any,
    public error?: Error,
    public retryable: boolean = true
  ) {}

  static success(data?: any): StepResult {
    return new StepResult(true, data);
  }

  static failure(error: Error, retryable: boolean = true): StepResult {
    return new StepResult(false, undefined, error, retryable);
  }
}

// Base saga implementation with orchestration logic
export abstract class BaseSaga implements ISaga {
  protected steps: SagaStep[] = [];
  protected executedSteps: SagaStep[] = [];
  protected state: SagaState = SagaState.NotStarted;
  protected currentStepIndex = 0;

  constructor(
    public readonly sagaId: string,
    public readonly sagaType: string
  ) {}

  protected addStep(step: SagaStep): void {
    this.steps.push(step);
  }

  async execute(): Promise<void> {
    this.state = SagaState.Running;

    try {
      for (let i = this.currentStepIndex; i < this.steps.length; i++) {
        const step = this.steps[i];
        console.log(`Executing saga step: ${step.name}`);

        const result = await this.executeStepWithRetry(step);

        if (!result.success) {
          if (result.retryable) {
            throw new SagaRetryableError(
              `Step ${step.name} failed: ${result.error?.message}`
            );
          } else {
            throw new SagaNonRetryableError(
              `Step ${step.name} failed: ${result.error?.message}`
            );
          }
        }

        this.executedSteps.push(step);
        this.currentStepIndex = i + 1;

        // Save progress for recovery
        await this.saveProgress();
      }

      this.state = SagaState.Completed;
      await this.saveProgress();
    } catch (error) {
      this.state = SagaState.Failed;
      await this.saveProgress();

      // Start compensation
      await this.compensate();
      throw error;
    }
  }

  async compensate(): Promise<void> {
    this.state = SagaState.Compensating;
    await this.saveProgress();

    // Compensate executed steps in reverse order
    const stepsToCompensate = [...this.executedSteps].reverse();

    for (const step of stepsToCompensate) {
      if (step.canCompensate()) {
        try {
          console.log(`Compensating saga step: ${step.name}`);
          await step.compensate();
        } catch (error) {
          console.error(`Compensation failed for step ${step.name}:`, error);
          // Continue compensating other steps even if one fails
        }
      }
    }

    this.state = SagaState.Compensated;
    await this.saveProgress();
  }

  private async executeStepWithRetry(
    step: SagaStep,
    maxRetries: number = 3
  ): Promise<StepResult> {
    let lastResult: StepResult;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        lastResult = await step.execute();

        if (lastResult.success) {
          return lastResult;
        }

        if (!lastResult.retryable) {
          return lastResult; // Don't retry non-retryable failures
        }

        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          console.log(
            `Step ${step.name} failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`
          );
          await this.sleep(delay);
        }
      } catch (error) {
        lastResult = StepResult.failure(error as Error);

        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000;
          console.log(
            `Step ${step.name} threw error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`
          );
          await this.sleep(delay);
        }
      }
    }

    return lastResult!;
  }

  getState(): SagaState {
    return this.state;
  }

  protected abstract saveProgress(): Promise<void>;

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

// Concrete saga for order processing
export class OrderProcessingSaga extends BaseSaga {
  private orderData: OrderData;
  private reservationId?: string;
  private paymentId?: string;
  private orderId?: string;
  private pointsTransactionId?: string;

  constructor(
    sagaId: string,
    orderData: OrderData,
    private inventoryService: IInventoryService,
    private paymentService: IPaymentService,
    private orderService: IOrderService,
    private loyaltyService: ILoyaltyService,
    private emailService: IEmailService,
    private sagaRepository: ISagaRepository
  ) {
    super(sagaId, "OrderProcessing");
    this.orderData = orderData;
    this.setupSteps();
  }

  private setupSteps(): void {
    // Step 1: Reserve inventory
    this.addStep(
      new InventoryReservationStep(
        this.inventoryService,
        this.orderData.items,
        (reservationId) => (this.reservationId = reservationId)
      )
    );

    // Step 2: Process payment
    this.addStep(
      new PaymentProcessingStep(
        this.paymentService,
        this.orderData.customerId,
        this.orderData.total,
        (paymentId) => (this.paymentId = paymentId)
      )
    );

    // Step 3: Create order
    this.addStep(
      new OrderCreationStep(
        this.orderService,
        this.orderData,
        () => this.paymentId!,
        (orderId) => (this.orderId = orderId)
      )
    );

    // Step 4: Add loyalty points (optional - can fail without breaking the flow)
    this.addStep(
      new LoyaltyPointsStep(
        this.loyaltyService,
        this.orderData.customerId,
        this.orderData.loyaltyPoints,
        (transactionId) => (this.pointsTransactionId = transactionId)
      )
    );

    // Step 5: Send confirmation email (optional - can fail without breaking the flow)
    this.addStep(
      new EmailConfirmationStep(
        this.emailService,
        () => this.orderData.customerEmail,
        () => this.orderId!
      )
    );
  }

  protected async saveProgress(): Promise<void> {
    const sagaState = {
      sagaId: this.sagaId,
      sagaType: this.sagaType,
      state: this.state,
      currentStepIndex: this.currentStepIndex,
      executedSteps: this.executedSteps.map((step) => step.name),
      data: {
        orderData: this.orderData,
        reservationId: this.reservationId,
        paymentId: this.paymentId,
        orderId: this.orderId,
        pointsTransactionId: this.pointsTransactionId,
      },
    };

    await this.sagaRepository.saveSagaState(sagaState);
  }

  // Factory method to restore saga from saved state
  static async restore(
    sagaState: any,
    inventoryService: IInventoryService,
    paymentService: IPaymentService,
    orderService: IOrderService,
    loyaltyService: ILoyaltyService,
    emailService: IEmailService,
    sagaRepository: ISagaRepository
  ): Promise<OrderProcessingSaga> {
    const saga = new OrderProcessingSaga(
      sagaState.sagaId,
      sagaState.data.orderData,
      inventoryService,
      paymentService,
      orderService,
      loyaltyService,
      emailService,
      sagaRepository
    );

    // Restore internal state
    saga.state = sagaState.state;
    saga.currentStepIndex = sagaState.currentStepIndex;
    saga.reservationId = sagaState.data.reservationId;
    saga.paymentId = sagaState.data.paymentId;
    saga.orderId = sagaState.data.orderId;
    saga.pointsTransactionId = sagaState.data.pointsTransactionId;

    // Restore executed steps
    for (let i = 0; i < sagaState.currentStepIndex; i++) {
      if (i < saga.steps.length) {
        saga.executedSteps.push(saga.steps[i]);
      }
    }

    return saga;
  }
}

// Individual saga steps with compensation logic
export class InventoryReservationStep implements SagaStep {
  name = "ReserveInventory";

  constructor(
    private inventoryService: IInventoryService,
    private items: OrderItem[],
    private onSuccess: (reservationId: string) => void
  ) {}

  async execute(): Promise<StepResult> {
    try {
      const reservation = await this.inventoryService.reserveItems(this.items);
      this.onSuccess(reservation.id);
      return StepResult.success(reservation);
    } catch (error) {
      return StepResult.failure(error as Error);
    }
  }

  async compensate(): Promise<void> {
    // Release the inventory reservation
    const reservationId = this.getReservationId();
    if (reservationId) {
      await this.inventoryService.releaseReservation(reservationId);
      console.log(`Released inventory reservation: ${reservationId}`);
    }
  }

  canCompensate(): boolean {
    return true;
  }

  private getReservationId(): string | undefined {
    // In a real implementation, this would be stored in the saga state
    // For simplicity, assuming it's available in the saga context
    return undefined;
  }
}

export class PaymentProcessingStep implements SagaStep {
  name = "ProcessPayment";

  constructor(
    private paymentService: IPaymentService,
    private customerId: string,
    private amount: number,
    private onSuccess: (paymentId: string) => void
  ) {}

  async execute(): Promise<StepResult> {
    try {
      const paymentResult = await this.paymentService.processPayment({
        customerId: this.customerId,
        amount: this.amount,
      });

      if (!paymentResult.success) {
        return StepResult.failure(
          new Error(`Payment failed: ${paymentResult.errorMessage}`),
          paymentResult.retryable
        );
      }

      this.onSuccess(paymentResult.transactionId);
      return StepResult.success(paymentResult);
    } catch (error) {
      return StepResult.failure(error as Error);
    }
  }

  async compensate(): Promise<void> {
    // Refund the payment
    const paymentId = this.getPaymentId();
    if (paymentId) {
      await this.paymentService.refundPayment(paymentId);
      console.log(`Refunded payment: ${paymentId}`);
    }
  }

  canCompensate(): boolean {
    return true;
  }

  private getPaymentId(): string | undefined {
    return undefined; // Would be stored in saga context
  }
}

export class OrderCreationStep implements SagaStep {
  name = "CreateOrder";

  constructor(
    private orderService: IOrderService,
    private orderData: OrderData,
    private getPaymentId: () => string,
    private onSuccess: (orderId: string) => void
  ) {}

  async execute(): Promise<StepResult> {
    try {
      const order = await this.orderService.createOrder({
        ...this.orderData,
        paymentId: this.getPaymentId(),
      });

      this.onSuccess(order.id);
      return StepResult.success(order);
    } catch (error) {
      return StepResult.failure(error as Error);
    }
  }

  async compensate(): Promise<void> {
    // Cancel the order
    const orderId = this.getOrderId();
    if (orderId) {
      await this.orderService.cancelOrder(orderId);
      console.log(`Cancelled order: ${orderId}`);
    }
  }

  canCompensate(): boolean {
    return true;
  }

  private getOrderId(): string | undefined {
    return undefined; // Would be stored in saga context
  }
}

export class LoyaltyPointsStep implements SagaStep {
  name = "AddLoyaltyPoints";

  constructor(
    private loyaltyService: ILoyaltyService,
    private customerId: string,
    private points: number,
    private onSuccess: (transactionId: string) => void
  ) {}

  async execute(): Promise<StepResult> {
    try {
      const transaction = await this.loyaltyService.addPoints(
        this.customerId,
        this.points
      );
      this.onSuccess(transaction.id);
      return StepResult.success(transaction);
    } catch (error) {
      // Loyalty points failure is not critical - log but don't fail the saga
      console.warn(`Failed to add loyalty points: ${error.message}`);
      return StepResult.success(); // Treat as success to not break the flow
    }
  }

  async compensate(): Promise<void> {
    // Remove the loyalty points
    const transactionId = this.getTransactionId();
    if (transactionId) {
      await this.loyaltyService.reverseTransaction(transactionId);
      console.log(`Reversed loyalty points transaction: ${transactionId}`);
    }
  }

  canCompensate(): boolean {
    return true;
  }

  private getTransactionId(): string | undefined {
    return undefined; // Would be stored in saga context
  }
}

export class EmailConfirmationStep implements SagaStep {
  name = "SendConfirmationEmail";

  constructor(
    private emailService: IEmailService,
    private getCustomerEmail: () => string,
    private getOrderId: () => string
  ) {}

  async execute(): Promise<StepResult> {
    try {
      await this.emailService.sendOrderConfirmation(
        this.getCustomerEmail(),
        this.getOrderId()
      );
      return StepResult.success();
    } catch (error) {
      // Email failure is not critical - log but don't fail the saga
      console.warn(`Failed to send confirmation email: ${error.message}`);
      return StepResult.success(); // Treat as success to not break the flow
    }
  }

  async compensate(): Promise<void> {
    // Emails cannot be "unsent" - nothing to compensate
    console.log("Email confirmation step - no compensation needed");
  }

  canCompensate(): boolean {
    return false; // Cannot compensate emails
  }
}

// Saga orchestrator that manages saga lifecycle
export class SagaOrchestrator {
  constructor(
    private sagaRepository: ISagaRepository,
    private serviceLocator: ServiceLocator
  ) {}

  async startSaga<T extends ISaga>(sagaFactory: () => T): Promise<string> {
    const saga = sagaFactory();

    try {
      await saga.execute();
      return saga.sagaId;
    } catch (error) {
      console.error(`Saga ${saga.sagaId} failed:`, error);
      throw error;
    }
  }

  async resumeSaga(sagaId: string): Promise<void> {
    const sagaState = await this.sagaRepository.getSagaState(sagaId);
    if (!sagaState) {
      throw new Error(`Saga state not found: ${sagaId}`);
    }

    const saga = await this.restoreSaga(sagaState);

    try {
      if (saga.getState() === SagaState.Failed) {
        await saga.compensate();
      } else if (saga.getState() === SagaState.Running) {
        await saga.execute(); // Resume execution
      }
    } catch (error) {
      console.error(`Failed to resume saga ${sagaId}:`, error);
      throw error;
    }
  }

  private async restoreSaga(sagaState: any): Promise<ISaga> {
    switch (sagaState.sagaType) {
      case "OrderProcessing":
        return await OrderProcessingSaga.restore(
          sagaState,
          this.serviceLocator.get("inventoryService"),
          this.serviceLocator.get("paymentService"),
          this.serviceLocator.get("orderService"),
          this.serviceLocator.get("loyaltyService"),
          this.serviceLocator.get("emailService"),
          this.sagaRepository
        );
      default:
        throw new Error(`Unknown saga type: ${sagaState.sagaType}`);
    }
  }
}

// Clean service using saga orchestration
export class OrderProcessingService {
  constructor(
    private sagaOrchestrator: SagaOrchestrator,
    private inventoryService: IInventoryService,
    private paymentService: IPaymentService,
    private orderService: IOrderService,
    private loyaltyService: ILoyaltyService,
    private emailService: IEmailService,
    private sagaRepository: ISagaRepository
  ) {}

  async processOrder(orderData: OrderData): Promise<ProcessOrderResult> {
    const sagaId = await this.sagaOrchestrator.startSaga(
      () =>
        new OrderProcessingSaga(
          uuidv4(),
          orderData,
          this.inventoryService,
          this.paymentService,
          this.orderService,
          this.loyaltyService,
          this.emailService,
          this.sagaRepository
        )
    );

    return new ProcessOrderResult(sagaId, "Order processing started");
  }

  async retryFailedOrder(sagaId: string): Promise<void> {
    await this.sagaOrchestrator.resumeSaga(sagaId);
  }
}

// Supporting types and interfaces
export interface ISagaRepository {
  saveSagaState(sagaState: any): Promise<void>;
  getSagaState(sagaId: string): Promise<any | null>;
}

export interface ServiceLocator {
  get<T>(serviceName: string): T;
}

export class SagaRetryableError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "SagaRetryableError";
  }
}

export class SagaNonRetryableError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "SagaNonRetryableError";
  }
}

export class ProcessOrderResult {
  constructor(
    public readonly sagaId: string,
    public readonly message: string
  ) {}
}

export interface OrderData {
  customerId: string;
  customerEmail: string;
  items: OrderItem[];
  total: number;
  loyaltyPoints: number;
}

export interface OrderItem {
  productId: string;
  quantity: number;
  price: number;
}

// Service interfaces for dependency injection
interface IInventoryService {
  reserveItems(items: OrderItem[]): Promise<{ id: string }>;
  releaseReservation(reservationId: string): Promise<void>;
}

interface IPaymentService {
  processPayment(request: { customerId: string; amount: number }): Promise<{
    success: boolean;
    transactionId: string;
    errorMessage?: string;
    retryable: boolean;
  }>;
  refundPayment(paymentId: string): Promise<void>;
}

interface IOrderService {
  createOrder(orderData: any): Promise<{ id: string }>;
  cancelOrder(orderId: string): Promise<void>;
}

interface ILoyaltyService {
  addPoints(customerId: string, points: number): Promise<{ id: string }>;
  reverseTransaction(transactionId: string): Promise<void>;
}

interface IEmailService {
  sendOrderConfirmation(email: string, orderId: string): Promise<void>;
}

Domain-Driven Design (DDD): Modeling Complex Business Domains

The Problem: Business Logic Scattered Across Technical Layers

// The anemic domain model that loses business meaning
class User {
  id: string;
  email: string;
  name: string;
  accountType: string;
  status: string;
  createdAt: Date;

  // Just getters and setters - no business logic
  constructor(data: any) {
    this.id = data.id;
    this.email = data.email;
    this.name = data.name;
    this.accountType = data.accountType;
    this.status = data.status;
    this.createdAt = data.createdAt;
  }
}

// Business logic scattered across services - RED FLAG #1
class UserService {
  async upgradeToSilverAccount(userId: string) {
    const user = await this.userRepository.findById(userId);

    // Business rules mixed with persistence logic
    if (user.accountType !== "basic") {
      throw new Error("Only basic accounts can be upgraded to silver");
    }

    if (user.status !== "active") {
      throw new Error("Account must be active for upgrade");
    }

    // How do we know if the user qualifies? Business logic here is wrong place
    const transactions = await this.transactionRepository.getByUserId(userId);
    const totalVolume = transactions.reduce((sum, t) => sum + t.amount, 0);

    if (totalVolume < 10000) {
      throw new Error("Minimum $10,000 transaction volume required for silver");
    }

    user.accountType = "silver";
    await this.userRepository.save(user);

    // More business logic scattered around
    await this.emailService.sendAccountUpgradeNotification(user.email);
    await this.auditService.logAccountUpgrade(userId, "silver");
  }
}

class SubscriptionService {
  async createSubscription(userId: string, planId: string) {
    const user = await this.userRepository.findById(userId);
    const plan = await this.planRepository.findById(planId);

    // Duplicate business rules - RED FLAG #2
    if (user.accountType === "basic" && plan.tier === "premium") {
      throw new Error("Basic accounts cannot subscribe to premium plans");
    }

    // Business logic that should be in domain model
    if (user.status === "suspended" && plan.allowSuspendedUsers !== true) {
      throw new Error("Suspended users cannot create subscriptions");
    }

    // Creating subscription without domain understanding
    const subscription = new Subscription({
      userId,
      planId,
      status: "active",
      startDate: new Date(),
      endDate: this.calculateEndDate(plan.duration),
    });

    await this.subscriptionRepository.save(subscription);
  }
}

The Solution: Rich Domain Model with DDD Principles

// Value Objects - immutable concepts with business meaning
export class Email {
  private readonly value: string;

  constructor(email: string) {
    if (!this.isValid(email)) {
      throw new InvalidEmailError("Email format is invalid");
    }
    this.value = email.toLowerCase().trim();
  }

  getValue(): string {
    return this.value;
  }

  equals(other: Email): boolean {
    return this.value === other.value;
  }

  getDomain(): string {
    return this.value.split("@")[1];
  }

  isBusinessEmail(): boolean {
    const businessDomains = [
      "gmail.com",
      "yahoo.com",
      "hotmail.com",
      "outlook.com",
    ];
    return !businessDomains.includes(this.getDomain());
  }

  private isValid(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email) && email.length <= 254;
  }
}

export class Money {
  constructor(
    private readonly amount: number,
    private readonly currency: string
  ) {
    if (amount < 0) {
      throw new InvalidAmountError("Money amount cannot be negative");
    }

    if (!currency || currency.length !== 3) {
      throw new InvalidCurrencyError("Currency must be a 3-letter code");
    }
  }

  getAmount(): number {
    return this.amount;
  }

  getCurrency(): string {
    return this.currency;
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new CurrencyMismatchError("Cannot add different currencies");
    }
    return new Money(this.amount + other.amount, this.currency);
  }

  multiply(factor: number): Money {
    return new Money(this.amount * factor, this.currency);
  }

  isGreaterThan(other: Money): boolean {
    if (this.currency !== other.currency) {
      throw new CurrencyMismatchError("Cannot compare different currencies");
    }
    return this.amount > other.amount;
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
}

export class TransactionVolume {
  constructor(private readonly transactions: Transaction[]) {}

  getTotalAmount(): Money {
    if (this.transactions.length === 0) {
      return new Money(0, "USD");
    }

    const currency = this.transactions[0].getAmount().getCurrency();
    let total = new Money(0, currency);

    for (const transaction of this.transactions) {
      total = total.add(transaction.getAmount());
    }

    return total;
  }

  getTransactionCount(): number {
    return this.transactions.length;
  }

  getAverageTransactionAmount(): Money {
    if (this.transactions.length === 0) {
      return new Money(0, "USD");
    }

    const total = this.getTotalAmount();
    return new Money(
      total.getAmount() / this.transactions.length,
      total.getCurrency()
    );
  }

  hasMinimumVolume(minimumAmount: Money): boolean {
    return this.getTotalAmount().isGreaterThan(minimumAmount);
  }

  getTransactionsInPeriod(startDate: Date, endDate: Date): Transaction[] {
    return this.transactions.filter(
      (t) => t.getTimestamp() >= startDate && t.getTimestamp() <= endDate
    );
  }
}

// Entities - objects with identity and lifecycle
export class Customer {
  private domainEvents: DomainEvent[] = [];

  constructor(
    private readonly id: CustomerId,
    private email: Email,
    private name: CustomerName,
    private accountType: AccountType,
    private status: CustomerStatus,
    private readonly registrationDate: Date,
    private transactionHistory: TransactionVolume = new TransactionVolume([])
  ) {}

  // Rich business behavior
  canUpgradeToSilver(): UpgradeEligibility {
    // Business rule: Only basic accounts can upgrade to silver
    if (!this.accountType.isBasic()) {
      return UpgradeEligibility.ineligible(
        "Only basic accounts can upgrade to silver"
      );
    }

    // Business rule: Account must be active
    if (!this.status.isActive()) {
      return UpgradeEligibility.ineligible(
        "Account must be active for upgrade"
      );
    }

    // Business rule: Minimum transaction volume required
    const minimumVolume = new Money(10000, "USD");
    if (!this.transactionHistory.hasMinimumVolume(minimumVolume)) {
      const currentVolume = this.transactionHistory.getTotalAmount();
      return UpgradeEligibility.ineligible(
        `Minimum $${minimumVolume.getAmount()} transaction volume required, current: $${currentVolume.getAmount()}`
      );
    }

    return UpgradeEligibility.eligible();
  }

  upgradeToSilver(): void {
    const eligibility = this.canUpgradeToSilver();
    if (!eligibility.isEligible()) {
      throw new AccountUpgradeError(eligibility.getReason());
    }

    const previousAccountType = this.accountType;
    this.accountType = AccountType.silver();

    // Emit domain event for side effects
    this.addDomainEvent(
      new CustomerAccountUpgradedEvent(
        this.id.getValue(),
        previousAccountType.getValue(),
        this.accountType.getValue(),
        this.transactionHistory.getTotalAmount()
      )
    );
  }

  canSubscribeTo(plan: SubscriptionPlan): SubscriptionEligibility {
    // Business rule: Basic accounts cannot subscribe to premium plans
    if (this.accountType.isBasic() && plan.isPremiumTier()) {
      return SubscriptionEligibility.ineligible(
        "Basic accounts cannot subscribe to premium plans"
      );
    }

    // Business rule: Suspended accounts have restrictions
    if (this.status.isSuspended() && !plan.allowsSuspendedUsers()) {
      return SubscriptionEligibility.ineligible(
        "Suspended users cannot create subscriptions for this plan"
      );
    }

    // Business rule: Must have minimum account age for certain plans
    if (plan.requiresMinimumAccountAge()) {
      const accountAge = Date.now() - this.registrationDate.getTime();
      const minimumAge = plan.getMinimumAccountAgeMs();

      if (accountAge < minimumAge) {
        return SubscriptionEligibility.ineligible(
          "Account does not meet minimum age requirement"
        );
      }
    }

    return SubscriptionEligibility.eligible();
  }

  createSubscription(plan: SubscriptionPlan, startDate: Date): Subscription {
    const eligibility = this.canSubscribeTo(plan);
    if (!eligibility.isEligible()) {
      throw new SubscriptionCreationError(eligibility.getReason());
    }

    const subscription = Subscription.create(this.id, plan, startDate);

    this.addDomainEvent(
      new CustomerSubscriptionCreatedEvent(
        this.id.getValue(),
        subscription.getId().getValue(),
        plan.getId().getValue(),
        startDate
      )
    );

    return subscription;
  }

  updateEmail(newEmail: Email): void {
    if (this.email.equals(newEmail)) {
      return; // No change needed
    }

    const previousEmail = this.email;
    this.email = newEmail;

    this.addDomainEvent(
      new CustomerEmailChangedEvent(
        this.id.getValue(),
        previousEmail.getValue(),
        newEmail.getValue()
      )
    );
  }

  updateTransactionHistory(transactionHistory: TransactionVolume): void {
    this.transactionHistory = transactionHistory;
  }

  // Domain event management
  getDomainEvents(): DomainEvent[] {
    return [...this.domainEvents];
  }

  clearDomainEvents(): void {
    this.domainEvents = [];
  }

  private addDomainEvent(event: DomainEvent): void {
    this.domainEvents.push(event);
  }

  // Getters for external access
  getId(): CustomerId {
    return this.id;
  }

  getEmail(): Email {
    return this.email;
  }

  getName(): CustomerName {
    return this.name;
  }

  getAccountType(): AccountType {
    return this.accountType;
  }

  getStatus(): CustomerStatus {
    return this.status;
  }

  getTransactionHistory(): TransactionVolume {
    return this.transactionHistory;
  }
}

// More Value Objects with business logic
export class CustomerId {
  constructor(private readonly value: string) {
    if (!value || value.trim().length === 0) {
      throw new InvalidIdError("Customer ID cannot be empty");
    }
  }

  getValue(): string {
    return this.value;
  }

  equals(other: CustomerId): boolean {
    return this.value === other.value;
  }
}

export class CustomerName {
  constructor(
    private readonly firstName: string,
    private readonly lastName: string
  ) {
    if (!firstName || firstName.trim().length === 0) {
      throw new InvalidNameError("First name cannot be empty");
    }

    if (!lastName || lastName.trim().length === 0) {
      throw new InvalidNameError("Last name cannot be empty");
    }
  }

  getFirstName(): string {
    return this.firstName;
  }

  getLastName(): string {
    return this.lastName;
  }

  getFullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }

  equals(other: CustomerName): boolean {
    return (
      this.firstName === other.firstName && this.lastName === other.lastName
    );
  }
}

export class AccountType {
  private static readonly BASIC = "basic";
  private static readonly SILVER = "silver";
  private static readonly GOLD = "gold";
  private static readonly PLATINUM = "platinum";

  private constructor(private readonly value: string) {}

  static basic(): AccountType {
    return new AccountType(AccountType.BASIC);
  }

  static silver(): AccountType {
    return new AccountType(AccountType.SILVER);
  }

  static gold(): AccountType {
    return new AccountType(AccountType.GOLD);
  }

  static platinum(): AccountType {
    return new AccountType(AccountType.PLATINUM);
  }

  static fromString(value: string): AccountType {
    switch (value) {
      case AccountType.BASIC:
        return AccountType.basic();
      case AccountType.SILVER:
        return AccountType.silver();
      case AccountType.GOLD:
        return AccountType.gold();
      case AccountType.PLATINUM:
        return AccountType.platinum();
      default:
        throw new InvalidAccountTypeError(`Invalid account type: ${value}`);
    }
  }

  getValue(): string {
    return this.value;
  }

  isBasic(): boolean {
    return this.value === AccountType.BASIC;
  }

  isSilver(): boolean {
    return this.value === AccountType.SILVER;
  }

  isGold(): boolean {
    return this.value === AccountType.GOLD;
  }

  isPlatinum(): boolean {
    return this.value === AccountType.PLATINUM;
  }

  canUpgradeTo(targetType: AccountType): boolean {
    const hierarchy = [
      AccountType.BASIC,
      AccountType.SILVER,
      AccountType.GOLD,
      AccountType.PLATINUM,
    ];
    const currentIndex = hierarchy.indexOf(this.value);
    const targetIndex = hierarchy.indexOf(targetType.value);

    return targetIndex === currentIndex + 1; // Can only upgrade to next level
  }

  equals(other: AccountType): boolean {
    return this.value === other.value;
  }
}

export class CustomerStatus {
  private static readonly ACTIVE = "active";
  private static readonly SUSPENDED = "suspended";
  private static readonly CLOSED = "closed";

  private constructor(private readonly value: string) {}

  static active(): CustomerStatus {
    return new CustomerStatus(CustomerStatus.ACTIVE);
  }

  static suspended(): CustomerStatus {
    return new CustomerStatus(CustomerStatus.SUSPENDED);
  }

  static closed(): CustomerStatus {
    return new CustomerStatus(CustomerStatus.CLOSED);
  }

  isActive(): boolean {
    return this.value === CustomerStatus.ACTIVE;
  }

  isSuspended(): boolean {
    return this.value === CustomerStatus.SUSPENDED;
  }

  isClosed(): boolean {
    return this.value === CustomerStatus.CLOSED;
  }

  getValue(): string {
    return this.value;
  }

  canTransitionTo(newStatus: CustomerStatus): boolean {
    // Business rules for status transitions
    switch (this.value) {
      case CustomerStatus.ACTIVE:
        return newStatus.isSuspended() || newStatus.isClosed();
      case CustomerStatus.SUSPENDED:
        return newStatus.isActive() || newStatus.isClosed();
      case CustomerStatus.CLOSED:
        return false; // Closed accounts cannot be reactivated
      default:
        return false;
    }
  }

  equals(other: CustomerStatus): boolean {
    return this.value === other.value;
  }
}

// Domain Services for complex business logic
export class UpgradeEligibility {
  private constructor(
    private readonly eligible: boolean,
    private readonly reason?: string
  ) {}

  static eligible(): UpgradeEligibility {
    return new UpgradeEligibility(true);
  }

  static ineligible(reason: string): UpgradeEligibility {
    return new UpgradeEligibility(false, reason);
  }

  isEligible(): boolean {
    return this.eligible;
  }

  getReason(): string {
    return this.reason || "No reason provided";
  }
}

export class SubscriptionEligibility {
  private constructor(
    private readonly eligible: boolean,
    private readonly reason?: string
  ) {}

  static eligible(): SubscriptionEligibility {
    return new SubscriptionEligibility(true);
  }

  static ineligible(reason: string): SubscriptionEligibility {
    return new SubscriptionEligibility(false, reason);
  }

  isEligible(): boolean {
    return this.eligible;
  }

  getReason(): string {
    return this.reason || "No reason provided";
  }
}

// Aggregate for subscription management
export class Subscription {
  private domainEvents: DomainEvent[] = [];

  private constructor(
    private readonly id: SubscriptionId,
    private readonly customerId: CustomerId,
    private readonly plan: SubscriptionPlan,
    private readonly startDate: Date,
    private endDate: Date,
    private status: SubscriptionStatus
  ) {}

  static create(
    customerId: CustomerId,
    plan: SubscriptionPlan,
    startDate: Date
  ): Subscription {
    const subscriptionId = new SubscriptionId(uuidv4());
    const endDate = plan.calculateEndDate(startDate);
    const status = SubscriptionStatus.active();

    return new Subscription(
      subscriptionId,
      customerId,
      plan,
      startDate,
      endDate,
      status
    );
  }

  cancel(reason: string): void {
    if (!this.status.isActive()) {
      throw new SubscriptionError("Only active subscriptions can be cancelled");
    }

    this.status = SubscriptionStatus.cancelled();

    this.addDomainEvent(
      new SubscriptionCancelledEvent(
        this.id.getValue(),
        this.customerId.getValue(),
        reason,
        new Date()
      )
    );
  }

  renew(newPlan?: SubscriptionPlan): void {
    if (!this.canRenew()) {
      throw new SubscriptionError(
        "Subscription cannot be renewed in current state"
      );
    }

    const planToUse = newPlan || this.plan;
    this.endDate = planToUse.calculateEndDate(this.endDate);
    this.status = SubscriptionStatus.active();

    this.addDomainEvent(
      new SubscriptionRenewedEvent(
        this.id.getValue(),
        this.customerId.getValue(),
        planToUse.getId().getValue(),
        this.endDate
      )
    );
  }

  private canRenew(): boolean {
    return this.status.isActive() || this.status.isExpired();
  }

  isExpired(): boolean {
    return new Date() > this.endDate;
  }

  getDaysUntilExpiry(): number {
    const now = new Date();
    const msUntilExpiry = this.endDate.getTime() - now.getTime();
    return Math.ceil(msUntilExpiry / (1000 * 60 * 60 * 24));
  }

  // Domain event management
  getDomainEvents(): DomainEvent[] {
    return [...this.domainEvents];
  }

  clearDomainEvents(): void {
    this.domainEvents = [];
  }

  private addDomainEvent(event: DomainEvent): void {
    this.domainEvents.push(event);
  }

  // Getters
  getId(): SubscriptionId {
    return this.id;
  }

  getCustomerId(): CustomerId {
    return this.customerId;
  }

  getPlan(): SubscriptionPlan {
    return this.plan;
  }

  getStartDate(): Date {
    return this.startDate;
  }

  getEndDate(): Date {
    return this.endDate;
  }

  getStatus(): SubscriptionStatus {
    return this.status;
  }
}

// Application Services orchestrating domain logic
export class CustomerService {
  constructor(
    private customerRepository: ICustomerRepository,
    private transactionRepository: ITransactionRepository,
    private eventPublisher: IDomainEventPublisher
  ) {}

  async upgradeCustomerToSilver(customerId: string): Promise<void> {
    // Load customer aggregate
    const customer = await this.customerRepository.findById(
      new CustomerId(customerId)
    );
    if (!customer) {
      throw new CustomerNotFoundError(customerId);
    }

    // Load transaction history for eligibility check
    const transactions = await this.transactionRepository.getByCustomerId(
      new CustomerId(customerId)
    );
    const transactionVolume = new TransactionVolume(transactions);
    customer.updateTransactionHistory(transactionVolume);

    // Execute business logic
    customer.upgradeToSilver();

    // Persist changes
    await this.customerRepository.save(customer);

    // Publish domain events
    const events = customer.getDomainEvents();
    for (const event of events) {
      await this.eventPublisher.publish(event);
    }

    customer.clearDomainEvents();
  }

  async createCustomerSubscription(
    customerId: string,
    planId: string,
    startDate: Date
  ): Promise<string> {
    // Load customer
    const customer = await this.customerRepository.findById(
      new CustomerId(customerId)
    );
    if (!customer) {
      throw new CustomerNotFoundError(customerId);
    }

    // Load subscription plan
    const plan = await this.subscriptionPlanRepository.findById(
      new PlanId(planId)
    );
    if (!plan) {
      throw new PlanNotFoundError(planId);
    }

    // Execute business logic
    const subscription = customer.createSubscription(plan, startDate);

    // Persist changes
    await this.subscriptionRepository.save(subscription);
    await this.customerRepository.save(customer);

    // Publish domain events
    const customerEvents = customer.getDomainEvents();
    const subscriptionEvents = subscription.getDomainEvents();

    for (const event of [...customerEvents, ...subscriptionEvents]) {
      await this.eventPublisher.publish(event);
    }

    customer.clearDomainEvents();
    subscription.clearDomainEvents();

    return subscription.getId().getValue();
  }
}

// Repository interfaces (implementing DDD Repository pattern)
export interface ICustomerRepository {
  findById(id: CustomerId): Promise<Customer | null>;
  save(customer: Customer): Promise<void>;
  findByEmail(email: Email): Promise<Customer | null>;
}

export interface IDomainEventPublisher {
  publish(event: DomainEvent): Promise<void>;
}

// Supporting types and errors
export class InvalidEmailError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidEmailError";
  }
}

export class InvalidAmountError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidAmountError";
  }
}

export class CurrencyMismatchError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "CurrencyMismatchError";
  }
}

export class AccountUpgradeError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "AccountUpgradeError";
  }
}

export class CustomerNotFoundError extends Error {
  constructor(customerId: string) {
    super(`Customer not found: ${customerId}`);
    this.name = "CustomerNotFoundError";
  }
}

// Domain Events
export class CustomerAccountUpgradedEvent extends DomainEvent {
  constructor(
    customerId: string,
    public readonly previousAccountType: string,
    public readonly newAccountType: string,
    public readonly transactionVolume: Money
  ) {
    super(customerId, "Customer");
  }

  getEventType(): string {
    return "CustomerAccountUpgraded";
  }
}

export class CustomerEmailChangedEvent extends DomainEvent {
  constructor(
    customerId: string,
    public readonly previousEmail: string,
    public readonly newEmail: string
  ) {
    super(customerId, "Customer");
  }

  getEventType(): string {
    return "CustomerEmailChanged";
  }
}

export class CustomerSubscriptionCreatedEvent extends DomainEvent {
  constructor(
    customerId: string,
    public readonly subscriptionId: string,
    public readonly planId: string,
    public readonly startDate: Date
  ) {
    super(customerId, "Customer");
  }

  getEventType(): string {
    return "CustomerSubscriptionCreated";
  }
}

Clean Architecture: Organizing Code for Maximum Flexibility

The Problem: Tightly Coupled Layers That Fight Change

// The layered architecture nightmare
class UserController {
  async getUser(req: Request, res: Response) {
    try {
      // Controller directly calling database - RED FLAG #1
      const user = await db.query("SELECT * FROM users WHERE id = ?", [
        req.params.id,
      ]);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      // Business logic in controller - RED FLAG #2
      if (user.subscription_end < new Date()) {
        user.status = "expired";
      }

      // External service call in controller - RED FLAG #3
      const paymentInfo = await stripe.customers.retrieve(
        user.stripe_customer_id
      );

      // Response formatting mixed with business logic - RED FLAG #4
      res.json({
        id: user.id,
        name: user.first_name + " " + user.last_name,
        email: user.email,
        status: user.status,
        subscription: {
          active: user.subscription_end > new Date(),
          end_date: user.subscription_end,
        },
        payment_method: paymentInfo.default_source,
      });
    } catch (error) {
      res.status(500).json({ error: "Internal server error" });
    }
  }
}

The Solution: Clean Architecture with Clear Boundaries

// Domain Layer (Core Business Logic - No Dependencies)
export class User {
  constructor(
    private readonly id: UserId,
    private readonly email: Email,
    private readonly name: UserName,
    private subscription: Subscription,
    private status: UserStatus
  ) {}

  // Pure business logic
  isSubscriptionActive(): boolean {
    return this.subscription.isActive();
  }

  canAccessPremiumFeatures(): boolean {
    return (
      this.isSubscriptionActive() &&
      this.status.isActive() &&
      this.subscription.includesPremiumFeatures()
    );
  }

  renewSubscription(newEndDate: Date): void {
    if (!this.subscription.canRenew()) {
      throw new SubscriptionRenewalError("Subscription cannot be renewed");
    }

    this.subscription = this.subscription.renewUntil(newEndDate);
  }

  // Getters for external access
  getId(): UserId {
    return this.id;
  }
  getEmail(): Email {
    return this.email;
  }
  getName(): UserName {
    return this.name;
  }
  getSubscription(): Subscription {
    return this.subscription;
  }
  getStatus(): UserStatus {
    return this.status;
  }
}

// Application Layer (Use Cases - Orchestrates Domain)
export interface GetUserUseCase {
  execute(request: GetUserRequest): Promise<GetUserResponse>;
}

export class GetUserUseCaseImpl implements GetUserUseCase {
  constructor(
    private userRepository: UserRepository,
    private paymentGateway: PaymentGateway,
    private presenter: GetUserPresenter
  ) {}

  async execute(request: GetUserRequest): Promise<GetUserResponse> {
    // Load user from repository
    const user = await this.userRepository.findById(request.userId);

    if (!user) {
      throw new UserNotFoundError(request.userId.getValue());
    }

    // Get payment information if needed
    let paymentMethod: PaymentMethod | null = null;
    if (request.includePaymentInfo) {
      try {
        paymentMethod = await this.paymentGateway.getPaymentMethod(
          user.getId()
        );
      } catch (error) {
        // Payment info is optional - don't fail the entire request
        console.warn("Failed to load payment info:", error);
      }
    }

    // Present the response
    return this.presenter.present(user, paymentMethod);
  }
}

// Interface Adapters Layer (Ports and Adapters)
export interface UserRepository {
  findById(id: UserId): Promise<User | null>;
  save(user: User): Promise<void>;
  findByEmail(email: Email): Promise<User | null>;
}

export interface PaymentGateway {
  getPaymentMethod(userId: UserId): Promise<PaymentMethod>;
  processPayment(request: PaymentRequest): Promise<PaymentResult>;
}

export interface GetUserPresenter {
  present(user: User, paymentMethod?: PaymentMethod | null): GetUserResponse;
}

// Infrastructure Layer (External Concerns - Implements Interfaces)
export class PostgresUserRepository implements UserRepository {
  constructor(private database: Database) {}

  async findById(id: UserId): Promise<User | null> {
    const query = `
      SELECT u.id, u.email, u.first_name, u.last_name, u.status,
             s.end_date, s.plan_type, s.features
      FROM users u
      LEFT JOIN subscriptions s ON u.id = s.user_id
      WHERE u.id = $1
    `;

    const result = await this.database.query(query, [id.getValue()]);

    if (result.rows.length === 0) {
      return null;
    }

    return this.mapToDomain(result.rows[0]);
  }

  async save(user: User): Promise<void> {
    const transaction = await this.database.beginTransaction();

    try {
      // Update user
      await transaction.query(
        `
        UPDATE users 
        SET email = $1, first_name = $2, last_name = $3, status = $4
        WHERE id = $5
      `,
        [
          user.getEmail().getValue(),
          user.getName().getFirstName(),
          user.getName().getLastName(),
          user.getStatus().getValue(),
          user.getId().getValue(),
        ]
      );

      // Update subscription
      await transaction.query(
        `
        UPDATE subscriptions
        SET end_date = $1, plan_type = $2
        WHERE user_id = $3
      `,
        [
          user.getSubscription().getEndDate(),
          user.getSubscription().getPlanType(),
          user.getId().getValue(),
        ]
      );

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  private mapToDomain(row: any): User {
    const userId = new UserId(row.id);
    const email = new Email(row.email);
    const name = new UserName(row.first_name, row.last_name);

    const subscription = new Subscription(
      new Date(row.end_date),
      SubscriptionPlan.fromString(row.plan_type),
      JSON.parse(row.features || "[]")
    );

    const status = UserStatus.fromString(row.status);

    return new User(userId, email, name, subscription, status);
  }
}

export class StripePaymentGateway implements PaymentGateway {
  constructor(private stripeClient: Stripe) {}

  async getPaymentMethod(userId: UserId): Promise<PaymentMethod> {
    // Map internal UserId to external Stripe customer ID
    const stripeCustomerId = await this.getStripeCustomerId(userId);

    const customer = await this.stripeClient.customers.retrieve(
      stripeCustomerId
    );

    if (!customer.default_source) {
      throw new NoPaymentMethodError(
        `No payment method for user ${userId.getValue()}`
      );
    }

    const paymentMethod = await this.stripeClient.paymentMethods.retrieve(
      customer.default_source as string
    );

    return new PaymentMethod(
      paymentMethod.id,
      paymentMethod.card?.brand || "unknown",
      paymentMethod.card?.last4 || "****"
    );
  }

  async processPayment(request: PaymentRequest): Promise<PaymentResult> {
    try {
      const paymentIntent = await this.stripeClient.paymentIntents.create({
        amount: request.amount * 100, // Convert to cents
        currency: request.currency,
        customer: await this.getStripeCustomerId(request.userId),
        description: request.description,
      });

      return PaymentResult.success(paymentIntent.id, paymentIntent.status);
    } catch (error) {
      return PaymentResult.failure(error.message);
    }
  }

  private async getStripeCustomerId(userId: UserId): Promise<string> {
    // Implementation would map internal user ID to Stripe customer ID
    // This could involve another database lookup or caching
    return `stripe_customer_id_for_${userId.getValue()}`;
  }
}

export class JsonGetUserPresenter implements GetUserPresenter {
  present(user: User, paymentMethod?: PaymentMethod | null): GetUserResponse {
    return new GetUserResponse(
      user.getId().getValue(),
      user.getName().getFullName(),
      user.getEmail().getValue(),
      user.getStatus().getValue(),
      {
        active: user.isSubscriptionActive(),
        endDate: user.getSubscription().getEndDate(),
        planType: user.getSubscription().getPlanType(),
        canAccessPremium: user.canAccessPremiumFeatures(),
      },
      paymentMethod
        ? {
            brand: paymentMethod.brand,
            lastFour: paymentMethod.lastFour,
          }
        : null
    );
  }
}

// Web Layer (Framework Specific - Outermost Layer)
export class UserController {
  constructor(private getUserUseCase: GetUserUseCase) {}

  async getUser(req: Request, res: Response): Promise<void> {
    try {
      const userId = new UserId(req.params.id);
      const includePaymentInfo = req.query.includePayment === "true";

      const request = new GetUserRequest(userId, includePaymentInfo);
      const response = await this.getUserUseCase.execute(request);

      res.json(response);
    } catch (error) {
      if (error instanceof UserNotFoundError) {
        res.status(404).json({ error: error.message });
      } else if (error instanceof InvalidUserIdError) {
        res.status(400).json({ error: error.message });
      } else {
        console.error("Unexpected error:", error);
        res.status(500).json({ error: "Internal server error" });
      }
    }
  }
}

// Request/Response Models (Data Transfer Objects)
export class GetUserRequest {
  constructor(
    public readonly userId: UserId,
    public readonly includePaymentInfo: boolean = false
  ) {}
}

export class GetUserResponse {
  constructor(
    public readonly id: string,
    public readonly name: string,
    public readonly email: string,
    public readonly status: string,
    public readonly subscription: {
      active: boolean;
      endDate: Date;
      planType: string;
      canAccessPremium: boolean;
    },
    public readonly paymentMethod: {
      brand: string;
      lastFour: string;
    } | null
  ) {}
}

// Dependency Injection Container (Composition Root)
export class DIContainer {
  private database: Database;
  private stripeClient: Stripe;

  constructor() {
    this.database = new Database(process.env.DATABASE_URL);
    this.stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY);
  }

  getUserController(): UserController {
    const userRepository = new PostgresUserRepository(this.database);
    const paymentGateway = new StripePaymentGateway(this.stripeClient);
    const presenter = new JsonGetUserPresenter();

    const getUserUseCase = new GetUserUseCaseImpl(
      userRepository,
      paymentGateway,
      presenter
    );

    return new UserController(getUserUseCase);
  }

  // Other factory methods for different controllers/use cases
}

// Domain Value Objects and Entities
export class UserId {
  constructor(private readonly value: string) {
    if (!value || value.trim().length === 0) {
      throw new InvalidUserIdError("User ID cannot be empty");
    }
  }

  getValue(): string {
    return this.value;
  }

  equals(other: UserId): boolean {
    return this.value === other.value;
  }
}

export class UserName {
  constructor(
    private readonly firstName: string,
    private readonly lastName: string
  ) {
    if (!firstName || firstName.trim().length === 0) {
      throw new InvalidNameError("First name cannot be empty");
    }
    if (!lastName || lastName.trim().length === 0) {
      throw new InvalidNameError("Last name cannot be empty");
    }
  }

  getFirstName(): string {
    return this.firstName;
  }

  getLastName(): string {
    return this.lastName;
  }

  getFullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
}

export class Subscription {
  constructor(
    private endDate: Date,
    private readonly planType: SubscriptionPlan,
    private readonly features: string[]
  ) {}

  isActive(): boolean {
    return new Date() <= this.endDate;
  }

  canRenew(): boolean {
    return !this.isActive() || this.getDaysUntilExpiry() <= 30;
  }

  renewUntil(newEndDate: Date): Subscription {
    if (newEndDate <= this.endDate) {
      throw new InvalidSubscriptionDateError(
        "New end date must be after current end date"
      );
    }

    return new Subscription(newEndDate, this.planType, this.features);
  }

  includesPremiumFeatures(): boolean {
    return this.planType.isPremium();
  }

  getDaysUntilExpiry(): number {
    const now = new Date();
    const msUntilExpiry = this.endDate.getTime() - now.getTime();
    return Math.ceil(msUntilExpiry / (1000 * 60 * 60 * 24));
  }

  getEndDate(): Date {
    return this.endDate;
  }

  getPlanType(): string {
    return this.planType.getValue();
  }
}

export class SubscriptionPlan {
  private static readonly BASIC = "basic";
  private static readonly PREMIUM = "premium";
  private static readonly ENTERPRISE = "enterprise";

  private constructor(private readonly value: string) {}

  static basic(): SubscriptionPlan {
    return new SubscriptionPlan(SubscriptionPlan.BASIC);
  }

  static premium(): SubscriptionPlan {
    return new SubscriptionPlan(SubscriptionPlan.PREMIUM);
  }

  static enterprise(): SubscriptionPlan {
    return new SubscriptionPlan(SubscriptionPlan.ENTERPRISE);
  }

  static fromString(value: string): SubscriptionPlan {
    switch (value) {
      case SubscriptionPlan.BASIC:
        return SubscriptionPlan.basic();
      case SubscriptionPlan.PREMIUM:
        return SubscriptionPlan.premium();
      case SubscriptionPlan.ENTERPRISE:
        return SubscriptionPlan.enterprise();
      default:
        throw new InvalidPlanTypeError(`Invalid plan type: ${value}`);
    }
  }

  getValue(): string {
    return this.value;
  }

  isPremium(): boolean {
    return (
      this.value === SubscriptionPlan.PREMIUM ||
      this.value === SubscriptionPlan.ENTERPRISE
    );
  }
}

export class UserStatus {
  private static readonly ACTIVE = "active";
  private static readonly INACTIVE = "inactive";
  private static readonly SUSPENDED = "suspended";

  private constructor(private readonly value: string) {}

  static active(): UserStatus {
    return new UserStatus(UserStatus.ACTIVE);
  }

  static inactive(): UserStatus {
    return new UserStatus(UserStatus.INACTIVE);
  }

  static suspended(): UserStatus {
    return new UserStatus(UserStatus.SUSPENDED);
  }

  static fromString(value: string): UserStatus {
    switch (value) {
      case UserStatus.ACTIVE:
        return UserStatus.active();
      case UserStatus.INACTIVE:
        return UserStatus.inactive();
      case UserStatus.SUSPENDED:
        return UserStatus.suspended();
      default:
        throw new InvalidStatusError(`Invalid status: ${value}`);
    }
  }

  isActive(): boolean {
    return this.value === UserStatus.ACTIVE;
  }

  getValue(): string {
    return this.value;
  }
}

export class PaymentMethod {
  constructor(
    public readonly id: string,
    public readonly brand: string,
    public readonly lastFour: string
  ) {}
}

export class PaymentRequest {
  constructor(
    public readonly userId: UserId,
    public readonly amount: number,
    public readonly currency: string,
    public readonly description: string
  ) {}
}

export class PaymentResult {
  private constructor(
    public readonly success: boolean,
    public readonly transactionId?: string,
    public readonly status?: string,
    public readonly errorMessage?: string
  ) {}

  static success(transactionId: string, status: string): PaymentResult {
    return new PaymentResult(true, transactionId, status);
  }

  static failure(errorMessage: string): PaymentResult {
    return new PaymentResult(false, undefined, undefined, errorMessage);
  }
}

// Domain Errors
export class UserNotFoundError extends Error {
  constructor(userId: string) {
    super(`User not found: ${userId}`);
    this.name = "UserNotFoundError";
  }
}

export class InvalidUserIdError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidUserIdError";
  }
}

export class SubscriptionRenewalError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "SubscriptionRenewalError";
  }
}

export class NoPaymentMethodError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "NoPaymentMethodError";
  }
}

Key Takeaways

Advanced architectural patterns aren’t just about organizing code—they’re about creating systems that can handle real-world business complexity while remaining maintainable and adaptable to change.

Essential advanced patterns for enterprise architecture:

  • CQRS separates read and write concerns for optimal performance and scalability
  • Event Sourcing captures complete business history for audit trails and business intelligence
  • Saga Pattern coordinates complex distributed workflows with compensation logic
  • Domain-Driven Design models business domains explicitly using rich domain objects
  • Clean Architecture organizes code in layers with clear dependency rules

The enterprise mindset:

  • Model the business domain: Code should reflect how the business actually works
  • Separate concerns completely: Commands, queries, events, and business logic should be isolated
  • Plan for distributed systems: Modern applications are inherently distributed
  • Build audit trails: Event sourcing provides complete business history by default
  • Think in bounded contexts: Large domains should be split into manageable subdomains

Implementation best practices:

  • Use value objects: Encapsulate business rules in immutable objects
  • Keep aggregates focused: Each aggregate should manage one business concept
  • Design for testability: Clean architecture makes unit testing natural
  • Handle failures gracefully: Saga patterns provide built-in compensation logic
  • Embrace eventual consistency: Not every operation needs immediate consistency

When to use advanced patterns:

  • Use CQRS when read and write performance requirements differ significantly
  • Use Event Sourcing when you need complete audit trails or complex business intelligence
  • Use Saga Pattern when you have long-running workflows across multiple services
  • Use DDD when the business domain is complex and changes frequently
  • Use Clean Architecture when you need maximum flexibility and testability

The architecture decision framework:

  • Start with simple layered architecture for basic CRUD applications
  • Add CQRS when read/write performance becomes problematic
  • Add Event Sourcing when audit requirements or business intelligence becomes critical
  • Add Saga Pattern when you need reliable distributed workflows
  • Use Clean Architecture principles from day one to maintain flexibility

What’s Next?

Congratulations! You’ve now mastered the advanced design patterns and architectural principles that power enterprise-grade systems. These patterns form the foundation for building applications that can handle millions of users, complex business domains, and constant change.

The next phase of your journey involves applying these patterns to specialized domains like scalability and high availability, performance optimization, and specialized backend topics that we’ll cover in the remaining blogs.

But more importantly, you now have the architectural mindset that separates senior developers from everyone else—the ability to look at a complex business problem and design a system that not only solves it today but can evolve gracefully as requirements change tomorrow.

Because the difference between good developers and great architects isn’t just knowing these patterns—it’s knowing when to use them, how to combine them, and when simpler solutions are actually better. That wisdom comes from building real systems that handle real complexity in production.