21 July, 2026

Building Better Salesforce Applications with Apex Enterprise Patterns

by Ana Urumovska

As Salesforce applications grow, so does the complexity of the code behind them. What starts as a few Apex classes and triggers can quickly become difficult to maintain when business logic is spread across controllers, triggers, Flows, Batch Apex, and REST APIs.

This is where Apex Enterprise Patterns come in. Built around the principle of Separation of Concerns (SoC), these patterns encourage you to organize your application into layers, each with a clear responsibility. The result is cleaner code, easier testing, improved reusability, and applications that are much easier to evolve over time.

In this post, we'll look at the four core patterns that make up a well-structured Salesforce application: Service Layer, Domain Layer, Selector Layer and Unit of Work.


Separation of Concerns

Separation of Concerns is the idea that each part of your application should have a single responsibility.

Rather than mixing UI logic, business rules, database queries, and DML operations in the same class, each concern is separated into its own layer. This makes your code easier to understand, easier to test, and far more maintainable.

Salesforce already encourages this approach through declarative tools like Flows and Validation Rules, but once you start writing Apex, it's up to you to keep your architecture organized.

The Service Layer

The Service Layer acts as the entry point for your application's business processes.

Instead of placing business logic inside Lightning controllers, REST resources, Batch Apex, or Invocable Methods, those components simply call a service method.

For example, whether a user applies a discount from a Lightning Web Component or an integration calls a REST endpoint, both should execute the same method: OpportunityService.applyDiscounts();
The Service Layer coordinates the process but doesn't need to know the details of how each object behaves.

The Domain Layer

The Domain Layer contains business logic that belongs to a specific object.

Think of calculations, validations, or behaviors that only concern an Opportunity, Account, or Invoice. Rather than implementing this logic in triggers or services, it lives inside a dedicated Domain class.

This keeps your Service Layer focused on orchestrating business processes while the Domain Layer handles object-specific behavior.

Triggers also become much simpler, acting only as entry points that delegate work to the appropriate Domain class.

The Selector Layer

The Selector Layer centralizes all database queries.

Instead of scattering SOQL throughout your codebase, selectors provide reusable methods for retrieving records.

Rather than writing inline queries like this:


your services simply call:


Centralizing queries improves consistency, reduces duplicated SOQL, and ensures callers always receive the fields they expect. It also provides a convenient place to enforce security and optimize queries as your application evolves.

Unit of Work

Many business operations update multiple records at once. Managing DML, SavePoints, rollbacks, and parent-child relationships manually can quickly become repetitive.

The Unit of Work pattern simplifies transaction management by collecting all inserts, updates, and deletes during a business operation and committing them together at the end.

How Everything Fits Together

These patterns work together to create a clean, layered architecture. Each layer has one responsibility:

  • Service Layer orchestrates business processes.

  • Domain Layer contains object-specific business logic.

  • Selector Layer retrieves data.

  • Unit of Work manages database transactions.

This separation keeps your code organized and makes it easier to introduce new features without affecting existing functionality.

Final Thoughts

Apex Enterprise Patterns aren't about creating more classes—they're about creating a codebase that's easier to maintain as your application grows.

You don't need to apply every pattern to every project. For smaller applications, Salesforce's declarative features may be enough. But as your business logic becomes more complex or needs to be reused across multiple entry points, adopting these patterns can significantly improve the quality and longevity of your code.

Building with Separation of Concerns in mind today can save countless hours of maintenance tomorrow.

Share: