Advanced 18 min read

Separation of Concerns: UI vs. Business Logic

By the end of this lesson, you'll be able to:

  • Distinguish UI rendering/interaction logic from genuine business logic
  • Explain why mixing the two in one place hurts maintainability and testability
  • Extract business logic into a reusable, independently testable location

Prerequisites: Module 15: "Exercise: Diagnose Realistic Broken Components"

What Is Separation of Concerns?

A component's JavaScript controller class should focus on UI concerns — reactive state, event wiring, rendering decisions. Genuine business logic — validation rules, calculations, data transformation — belongs somewhere more reusable: either an Apex service class (server-side, Lesson 2) or a plain, importable JavaScript utility module (client-side, using Module 4's import/export).

Why Mixing Them Hurts

Business logic embedded directly inside a component's event handler is hard to reuse in a second component that needs the same rule, and hard to unit test in isolation from the component's rendering — a Jest test (Module 14) ends up needing to simulate a whole component just to verify a calculation that has nothing to do with rendering at all.

A Before/After Example

// Before — business logic buried inside a UI event handler
handleApplyDiscount() {
    let discount = 0;
    if (this.orderTotal > 1000) {
        discount = this.orderTotal * 0.1;
    } else if (this.orderTotal > 500) {
        discount = this.orderTotal * 0.05;
    }
    this.finalTotal = this.orderTotal - discount;
}
// discountUtils.js — reusable, independently testable
export function calculateDiscount(orderTotal) {
    if (orderTotal > 1000) return orderTotal * 0.1;
    if (orderTotal > 500) return orderTotal * 0.05;
    return 0;
}
// After — the component delegates the actual calculation
import { calculateDiscount } from './discountUtils';

handleApplyDiscount() {
    const discount = calculateDiscount(this.orderTotal);
    this.finalTotal = this.orderTotal - discount;
}

calculateDiscount can now be tested directly with plain values, reused by any other component that needs the same rule, and changed in one place rather than several.

Where This Applies Server-Side Too

The same principle applies to Apex controllers exposed via @AuraEnabled (Module 7) — they should stay thin, delegating real business logic to separate service classes rather than embedding it directly in the LWC-facing method. Lesson 2 covers this exact layering in depth.

Exercise

Extract this inline validation logic into a standalone, exported isValidEmail function.

Show hint

Move the logic out of the handler into its own module-level function.

JAVASCRIPT

Exercise

Challenge: explain, as a comment, why extracting calculateDiscount makes it easier to unit test than leaving it inline in handleApplyDiscount.

Show hint

Think about what a test would need to set up in each case.

JAVASCRIPT

Separation of Concerns: UI vs. Business Logic Quiz

1. What should a component's controller class primarily focus on?

2. Why is business logic embedded in a click handler hard to reuse?

3. What makes calculateDiscount easier to unit test once extracted?

4. Does this separation-of-concerns principle apply to Apex controllers as well as LWC components?

5. What is a genuine downside of mixing UI and business logic in one place?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A component's controller class should focus on UI state and event wiring — genuine business rules belong somewhere reusable and independently testable, not buried inside a click handler.