Skip to main content

Why Trigger Handlers Break at Scale in Salesforce

๐Ÿ’ฌ In plain words: A trigger handler is a hallway, not a house. When a Salesforce org scales up, if you don't have separate rooms (layers) for your logic, everything piles into that one hallway. Eventually, you get a massive, fragile class where every new change risks breaking everything else. Layered architecture exists precisely to solve this problem.
๐Ÿ“Œ Example: In Year 1, your AccountTriggerHandler is a tidy 300 lines of code. By Year 4, it has swollen to 5,000 lines. Fourteen different developers are committing changes to it, and every deployment seems to break an unrelated feature. You've hit the "God-handler" ceiling. You need to split it up—because a hallway can't hold an entire house.
๐ŸŽฌ Real-Life Example: The 5,000-Line Hallway

Imagine a class called DeliveryTriggerHandler that is five years old and 5,000 lines long.

The Old/Bad Way: Every new feature just became one more method stuffed into the same class. Billing, SMS notifications, and delivery routing all live in one massive file.

Why it is bad: Every time you change SMS logic, you risk breaking Billing. Two teams constantly collide in the same file. Unit tests take an hour to run because testing one small piece drags the entire 5,000-line behemoth into memory. Deployments are terrifying.

The New/Good Way:
  1. Shrink the handler so it is just a thin router.
  2. Move the actual business logic into specific Domain or Service classes.
  3. Extract database queries into Selector classes as you encounter them.
  4. Add targeted unit tests for these new, smaller classes as you go.
The payoff: Your crowded hallway becomes a well-organized corridor of clearly labeled rooms. Code changes shrink from scary events to boring, predictable tasks.
๐Ÿง  Hallway, not house: A handler is one hallway everyone crowds into. Layered architecture gives each concern its own room.

Concept: Why Trigger Handlers Are Not Enough

A basic trigger-handler framework is great for organizing the entry point of an execution context. But it says absolutely nothing about where the core business logic, the database queries (SOQL), or the database updates (DML) should actually live. At scale, the handler simply becomes the new monolith.

  • The 1,000-Line Trap: You end up with massive handler classes. Worse, because the logic is trapped inside the handler, the only way to test a simple calculation is to perform expensive DML operations to fire the trigger.
  • Smudged Logic: The exact same SOQL query gets duplicated across multiple files. Complex, cross-object business processes get smeared across three or four different object handlers.
  • Architectural Gravity: Without a designated, structured home for business logic, code just accretes at the entry point like a snowball rolling downhill.
Diagram showing how Trigger Handlers become bloated monoliths at scale
๐Ÿงญ 360 Card — When Trigger-Handler Alone Breaks

Rule: A handler organizes the entry point. It does not dictate where logic, queries, and DML belong.
Gain: Knowing the symptoms allows you to diagnose exactly when an org has outgrown a basic framework, based on evidence rather than opinion.
Price: Refactoring later is more expensive than starting with layered architecture on Day 1. However, delaying layers is still usually the right business trade-off for early-stage projects.
Limits: The breaking points are obvious: Handlers exceeding a few hundred lines. Bizarre method names like handleAfterUpdate2. Business logic that is impossible to test without inserting records. Teams terrified to deploy.
Mirror (Starting every project with full layers): If you build full Service, Domain, and Selector layers on Day 1 for a tiny project, you pay a heavy tax in boilerplate classes, indirection, and developer onboarding time for a complexity you don't yet have.
Later: When you do need to migrate, use the Strangler Pattern. Freeze the old handler, build the new layers alongside it, and move cohesive slices of logic over one at a time.
At volume: This architectural breakdown is caused by team size and code age, not data volume.
⚠ INTERVIEW TRAP:
When discussing migration, leaving the old, bloated code path running "just in case" is a classic junior mistake. If you rewrite a method, delete the old one in the exact same Pull Request. Otherwise, your team is now maintaining two parallel implementations forever.

Core Q&A

Q: What are the observable symptoms that an org has outgrown a trigger-handler-only architecture?

๐ŸŽฏ Say this first: The signals are God classes, deployments that break unrelated features, rampant duplicate queries, untestable business logic, and teams that are afraid to release code.

A: Look for these five distinct symptoms:

  • Bloat: Handlers ballooning past a few hundred lines, often featuring desperate naming conventions like handleAfterUpdate2.
  • Slow Tests: Business logic that can only be invoked by triggering DML. Unit tests take minutes to run because every single assertion requires a slow database insert just to reach the code.
  • Query Duplication: The exact same SOQL query (perhaps with slightly different field lists) appears in five different places.
  • Scattered Processes: A cross-object workflow (like Quote to Order to Invoice) is implemented as a messy relay race across multiple triggers. Understanding the flow requires reading three different handlers and guessing the execution order.
  • Trapped Logic: When asked, "Where do I put this logic so it can be called by both a REST endpoint AND a trigger?", the team has no answer.

Each of these symptoms maps directly to a missing architectural layer: Domain, Service, or Selector.

Follow-ups (Scenario-Based)

Q: How do you incrementally migrate a 2,000-line handler without attempting a massive, risky rewrite?

A: You use the Strangler Pattern. First, freeze the existing handler—absolutely no new logic goes in.

  • Stand up the empty target layers (Service, Domain, Selector).
  • Move one cohesive slice of functionality at a time.
  • Start with the queries: Move them into a Selector class. This is mechanical, low-risk work that provides immediate deduplication benefits.
  • Extract behaviors: Move business logic into Service or Domain methods that the original handler now simply calls. The handler should rapidly shrink down to just routing traffic.
  • Ride funded work: Prioritize refactoring the slices of code that you are already scheduled to touch for upcoming feature work.
  • Enforce two rules: Write tests for the new slice first, and delete the old code path in the very same Pull Request.

Q: A new requirement arrives: When an Invoice is paid, we must close related Cases, update the Account tier, and notify billing. Where does each piece live and why?

A: This is an orchestration task, so the master coordinator is a Service method: InvoiceService.handlePayment. This is a high-level business use-case that spans multiple objects.

  • Today, this Service method is invoked by the Invoice trigger. Tomorrow, it could be invoked seamlessly by a payment webhook without duplicating any code.
  • The specific rules for closing Cases belong in the Case Domain, because defining what "closed" means for a Case is the Case object's own business.
  • The math for calculating the Account tier belongs in the Account Domain (or a dedicated calculator class).
  • All queries are routed through Selectors.
  • All DML is routed through a Unit of Work, which ensures everything commits safely in the correct dependency order.
  • Finally, the billing notification should be an Event, published only after the database commit is successful.

Interview Tip: Narrating the placement of logic exactly like this shows the interviewer that you view layers as strict decision rules, not just buzzwords.

Q (Compare): If trigger handlers always break at scale, why not just start every single project with full layers?

A: Cost. Implementing layers adds classes, indirection, boilerplate, and developer onboarding time. That is real money spent on a small project. The correct approach is to start simple and refactor exactly when the warning signals appear. Architecture is a purchase you time carefully, not an automatic default.