All skills
Skillintermediate

Enforce Separation of Concerns Between Layers

Do NOT mix business logic with UI components or place database queries directly in controllers. Each architectural layer must have a single responsibility: controllers handle HTTP concerns, services encapsulate business logic, and repositories manage data access. Violating these boundaries creates tightly coupled code that is difficult to test, refactor, and reason about. When business rules live

Claude Code Knowledge Pack7/10/2026

Overview

Enforce Separation of Concerns Between Layers

Do NOT mix business logic with UI components or place database queries directly in controllers. Each architectural layer must have a single responsibility: controllers handle HTTP concerns, services encapsulate business logic, and repositories manage data access. Violating these boundaries creates tightly coupled code that is difficult to test, refactor, and reason about. When business rules live inside controllers, they cannot be reused across different entry points (API, CLI, events) and changes to infrastructure leak into domain logic. Maintain clear boundaries between contexts by delegating work through well-defined interfaces rather than inlining cross-cutting concerns.

Critical principles

  • Do NOT mix business logic with UI components
  • Keep database queries out of controllers
  • Maintain clear boundaries between contexts
  • Ensure proper separation of responsibilities

Incorrect

The controller mixes HTTP handling, business logic, and database queries in a single function, making it impossible to reuse or test the business rules independently.

// OrderController.ts — everything in one place

  async createOrder(req: Request, res: Response) {
    const { items, customerId } = req.body;

    // Database query directly in controller
    const customer = await db.query("SELECT * FROM customers WHERE id = $1", [customerId]);
    if (!customer) {
      return res.status(404).json({ error: "Customer not found" });
    }

    // Business logic mixed into controller
    let total = 0;
    for (const item of items) {
      const product = await db.query("SELECT * FROM products WHERE id = $1", [item.productId]);
      total += product.price * item.quantity;
    }
    if (total > 10000) {
      total = total * 0.9; // 10% discount for large orders
    }

    // More database queries inline
    const order = await db.query(
      "INSERT INTO orders (customer_id, total) VALUES ($1, $2) RETURNING *",
      [customerId, total]
    );

    return res.status(201).json(order);
  }
}

Correct

The controller delegates to a service for business logic and a repository for data access. Each layer has a single responsibility and can be tested and reused independently.

// OrderController.ts — handles HTTP only

  constructor(private orderService: OrderService) {}

  async createOrder(req: Request, res: Response) {
    const { items, customerId } = req.body;
    const order = await this.orderService.createOrder(customerId, items);
    return res.status(201).json(order);
  }
}

// OrderService.ts — business logic only

  constructor(
    private customerRepo: CustomerRepository,
    private productRepo: ProductRepository,
    private orderRepo: OrderRepository
  ) {}

  async createOrder(customerId: string, items: OrderItem[]): Promise {
    const customer = await this.customerRepo.findById(customerId);
    if (!customer) {
      throw new NotFoundError("Customer not found");
    }

    const total = await this.calculateTotal(items);
    return this.orderRepo.create({ customerId, total });
  }

  private async calculateTotal(items: OrderItem[]): Promise<number> {
    let total = 0;
    for (const item of items) {
      const product = await this.productRepo.findById(item.productId);
      total += product.price * item.quantity;
    }
    return total > 10000 ? total * 0.9 : total;
  }
}

// OrderRepository.ts — data access only

  async create(data: CreateOrderData): Promise {
    return db.query(
      "INSERT INTO orders (customer_id, total) VALUES ($1, $2) RETURNING *",
      [data.customerId, data.total]
    );
  }
}

Reference