Script Valley
Writing Clean Code: Naming, Functions & Structure
Code Structure and OrganizationLesson 3.5

How to separate business logic from infrastructure code

hexagonal architecture basics, business logic isolation, infrastructure dependencies, dependency injection, testability through separation

Separating Business Logic from Infrastructure

Business logic and infrastructure separation

Business logic is the code that encodes what your application actually does — the rules, calculations, and decisions. Infrastructure is everything the application depends on to run: databases, HTTP clients, email services, file systems.

The rule: business logic should never directly import infrastructure code. This one rule makes business logic independently testable without a real database or network.

// Bad — business logic directly depends on infrastructure
function applyLoyaltyDiscount(userId) {
  const user = db.query('SELECT * FROM users WHERE id = ?', userId);
  if (user.orders_count > 10) {
    db.query('UPDATE users SET discount = 0.15 WHERE id = ?', userId);
  }
}

Separate the logic from the data access:

// Pure business logic — no infrastructure
function calculateLoyaltyDiscount(ordersCount) {
  return ordersCount > 10 ? 0.15 : 0;
}

// Infrastructure layer calls the logic
async function applyLoyaltyDiscount(userId) {
  const user = await userRepository.findById(userId);
  const discount = calculateLoyaltyDiscount(user.ordersCount);
  await userRepository.updateDiscount(userId, discount);
}

Now calculateLoyaltyDiscount is a pure function you can test with zero setup. The infrastructure layer handles all I/O and calls the pure logic with real data.

This pattern — often called hexagonal or ports-and-adapters architecture — scales from small functions to entire applications. You don't need a framework to apply it: just stop letting business logic directly call databases and HTTP clients.