Skip to main content
01 Separation of ConcernsChapter 01 of 2020 Force DI
Chapter 01 · Introduction

Introduction to the Separation of Concerns Design Principle

An explanation of the Separation of Concerns Design Principle and why it is valuable to implement in any codebase.

6 min readVideo 18:42Standard template

Who This Guide is For

This guide is for developers building things on Salesforce that are meant to last: org developers in businesses whose processes live on the platform, consultants inheriting an "unknown vault" of someone else's code, and ISV developers whose enterprise customers invest as much in the application as they do in the vendor behind it - building their own processes on top and expecting it all to keep working for years. Salesforce's own documentation is excellent but reference-style; what's hard to piece together from reference docs is a path from an empty org to an architecture. That path is what this guide (and its companion videos) tries to be.


Five Questions That Tell You Whether You Have SoC

Before we define anything, try scoring your current org against these five questions:

  1. If you had to replace or add another UI to your app - say, migrating an old Aura component to LWC, or adding a Flow screen over the same process - how much code would you be rewriting that actually has nothing to do with the UI? How much of it is really record inserting, updating, validating and calculating logic that got stuck in a controller?

  2. If you were asked to expose your app's core logic as a public API tomorrow, what would the API implementation actually call? Do clean entry points exist, or would you be scraping logic out of @AuraEnabled methods and passing around bits of component state?

  3. If a process that runs interactively today needed to also run at scale from Batch Apex or a Queueable, could both paths share the same logic and guarantee the user identical results? Or would you be copy-pasting it into the batch class?

  4. Does your code merely use an MVC-ish framework, or does it actually separate concerns? Using LWC with Apex controllers doesn't guarantee SoC any more than Visualforce and controllers did - the tell is how fat your controller methods are. Is there code in them that does more than shuttle content to and from the user?

  5. How easily can a new developer find their way around your codebase? How long before they know where new code should go and where existing behavior lives?

If a few of those made you wince, good news: this entire guide is the treatment plan.


What is the Separation of Concerns Design Principle?

Separation of concerns is the practice of putting logical boundaries on your code. Putting these logical boundaries on your code helps make your code easier to understand, easier to maintain and much more flexible when it needs to be altered (and every codebase ever has to be altered all the time).

In the Salesforce Ecosystem there are three major areas of concern to separate our code into. They are the following:

The Service Layer:

The Service Layer should house 100% of your non-object-specific business logic (object-specific logic is most often handled by the domain layer). That is, the logic that is specific to your organization's business rules. Say for instance you have a part of your Salesforce App that focuses on Opportunity Sales Projections and the Opportunity Sales Projection App looks at the Opportunity, Quote, Product and Account objects. You might make an OpportunitySalesProjection_Service Apex class that houses methods that have business logic that is specific to your Opportunity Sales Projection App. More information on the Service Layer here.

The Domain Layer:

The Domain Layer houses the trigger logic for your individual objects (database tables). It also houses object-specific validation logic, logic that should always be applied on the insert of every record for an object and object-specific business logic (like how a task may be created for a specific object type, etc). If you used the Account object in your org you should create a Domain class equivalent for the Account object through the use of a trigger handler class of some sort. More information on the Domain Layer here.

The Selector Layer:

The Selector Layer is responsible for querying your objects (database tables) in Salesforce. Selector layer classes should be made for each individual object (or grouping of objects) that you intend to write queries for in your code. The goal of the selector layer is to maintain query consistency (consistency in ordering, common fields queried for, etc) and to be able to reuse common queries easily and not re-write them over and over again everywhere.


Why is it Useful?

First, let's clear up the most common misconception right away.

SoC is not just a posh word for code reuse

Code reuse is the ad-hoc thing that happens when two places need the same fragment, so it gets moved into a MyUtil class or some other generic dumping ground. That's fine - certainly better than copy and paste! But SoC is upfront thinking about the internal plumbing of your application - down to the class naming conventions and coding guidelines - done so the structure endures and is self-describing to the developers who come after you. Reuse is a side effect of SoC; it isn't the point of it.

The three durable benefits

Think of any application as having three parts: storage, logic, and one or more means of interaction (human UIs, APIs, automation). Once you've outlined those, you can define layers, each with its own responsibilities to the application and to the other layers. Managing those layers carefully buys you three things:

  1. Evolution. Over time, technology, understanding and requirements all change, and any single layer may need to be significantly extended, reworked or dropped entirely. Just look at UI technology on this platform: SControls -> Visualforce → Aura → LWC → Multi-framework -> Flow screens → Agentforce, the UI just never stops evolving. Layers let one part evolve without dragging the rest with it. Software that endures survives its own staff turnover, too - the structure has to outlive the specific developers who built it.

  2. Impact Management. Working on - or even deleting - one layer should not unduly impact the others. That's what makes change estimable: when a request comes in, you can name which layer it touches and be confident about what it won't.

  3. Roles and Responsibility. Each layer has its own responsibility and should neither under-deliver nor over-extend it. Dropping one client technology for another should never mean losing business logic, because business logic was never the UI layer's responsibility to hold. Be warned: if the lines of responsibility get blurred, the purpose and value of SoC erode with them.

And the day-to-day wins

  1. Modularizes your code into easy to understand packages of code making it easier to know what code controls what, why and when.

  2. Helps improve code navigation and reduces the amount of code sprawl in your org by centralizing your logic into different containers. For instance, maybe you currently have 13 different apex controllers that house similar case business logic. If you placed that business logic into a service class and had all 13 apex controllers call that service class instead your life would be a whole lot simpler.

  3. Separation of Concerns lends itself to writing well done and comprehensive Unit Tests. It allows for easy dependency injection which allows you to, in test classes, mock a class's dependent classes. We'll go over this more when we get to the Unit testing and Apex Mocks section of this tutorial (and dependency injection gets a full treatment of its own in chapter 18), but if you want a quick and easy explanation, please feel free to take a look at this video covering dependency injection and mocking in Apex.


The Layer Above the Code

One more layer deserves naming, because it's invisible in every architecture diagram drawn by developers: the declarative layer. Objects, fields, validation rules, flows, formula fields - built without a line of code - are still very much an architecture layer of your application, and on this platform they should be your first port of call. If your app is heavily data-centric, a large portion of it can and should be delivered declaratively. Code is what you reach for when the app is process-centric or pushes past what the declarative tools can express: complex calculations, complex validation, rich UI experiences.

Two disciplines follow from that. First, code only where you need to - an Apex trigger that reimplements what a validation rule could do is a maintenance liability, not engineering. Second, when you do write code, remember that Apex has a lot of doors: triggers, LWC/Aura controllers, REST services, Invocable Methods, Batch Apex, Queueables, email handlers, platform event subscribers. Of everything you invest developer and testing hours in, business logic is the asset you most need to protect - which means it can't live scattered behind whichever door happened to be built first. The layers this guide teaches are, at bottom, a system for keeping the many doors thin and the valuable logic in one defensible place.


How does the Apex Common Library help with SoC?

The Apex Common Library was built with the Separation of Concerns design principle and the three layers outlined above in mind (Service, Selector and Domain Layers). It provides a solid foundation to implement SoC in your Salesforce org.


Example Code

There are two places to get example code you can reference:

  1. There is example code that is updated and maintained in the fflib-apex-common-samplecode GitHub repo.
  2. There is example code in the repo that hosts this doc site. The code is located here. For layer specific code examples check out the layer specific pages of this doc site, they all host example code you can reference.