Best Practices
MISSING SUBTEXT

How to Manage Your Apex Codebase for Scale and Agility

Veteran Salesforce developer Hank Holiday offers design patterns to help you structure your Apex code in Salesforce so that it is maintainable for the long run.

June 3, 2020

This is a guest post by Hank Holiday, a senior Salesforce developer with over 10 years of experience writing Salesforce code at organizations large and small.

Introduction

Apex is obviously one of the most powerful features of Salesforce. It’s also one of the easiest ways to rapidly create technical debt within your Salesforce org. After more than a decade of work as a Salesforce engineer and architect, I’ve gotten the chance to dive into Apex codebases at dozens of different organizations at various levels of maturity. And more often than not what I find upon first inspection is a complete mess:

  • Multiple triggers for the same object with overlapping functionality.
  • Massive trigger files with the business logic and record filtering logic all bunched together in a big monolithic chunk.
  • Boilerplate chunks of code copied and pasted between classes and methods throughout the codebase.
  • Critical constant values hard-coded in Apex (things like record type ids, admin email addresses, user ids, API urls and more).
  • A jumble of inscrutable file names like “qtrReportSummaryCont.cls” and “CoTrig_v4_JakeApr15.trigger”
  • Code that has been edited in the developer console so many times that it no longer has any discernible indentation pattern.

Some of these may seem like fairly innocuous issues, but they’re not. These issues add up over time, leading to code bloat and code smell (more code smell examples). Eventually this leads to user-facing impacts like bugs, data loss, difficulty in deploying new features and even mini “revolts” where whole teams give up and revert to doing certain tasks in spreadsheets.

The good news is that there are a multitude of easy-to-follow best practices and fast-to-implement design patterns which can help save your team time and make it easier to manage your entire Salesforce implementation. What follows is a collection of pro-tips, examples and code snippets which should help you lay down a solid foundation for your Apex development work.

Adopting these practices can help a small team get more done and help keep a big team from getting stuck in the weeds.

Before you begin - are you using version control?

First Things First

Are you tracking your Apex code in a source-control management system (like GitHub or SFDC-specific source control tools like Blue Canvas)? If you aren’t tracking your Apex in a git-based repository of some sort yet, stop reading this article and fix that problem first, then come back.

Source control is the first and most important tool in your toolbox. If you aren’t using it today then you are flying blind. Without the ability to do code reviews on each change coming into production you will struggle to implement even the most basic of best practices. And without detailed change histories on your codebase, retrospective debugging is largely impossible.

Have your source control on point? Great, let’s move on.

Foundational Principles

All of the recommendations in this article are grounded in a set of basic guiding principles:

  • Optimize for Clarity: The primary goal of all of this strategizing is to optimize for efficient development work. You want to be able to understand your code at a glance and debug issues rapidly. And you need to deploy enhancements without having to trace massive execution paths or weeding through pages of code.
  • Readability Reigns: One of our most powerful assets is readable code. Good formatting and understandable variable/function names aren’t about making code “pretty”. These things are about reducing the amount of time it takes a new developer (or yourself 6 months from now) to understand what a piece of code does.
  • Always be Standardizing: Our git repo is our source of truth and code reviews on deployment/pull requests are our primary way to ensure quality. Reducing git noise caused by off-target changes (like corrections to things like indentation or variable names) is a key way to get there. We do this by having standard ways of doing things and aiming for a reasonable amount of consistency between developers.
  • Keep Code DRY: Don’t Repeat Yourself, design your codebase so that it makes it as easy as possible to reuse code.
  • Each Class does One Thing: Apex is an object-oriented language and one of the key choices we can mess up is how and when we create (or don’t create) abstractions. The Single-Responsibility Principle is a powerful tool and every team member should be familiar with it.

Tools & Process

It all starts with the tools we use to do our jobs and the way we approach our work.

Use Visual Studio Code and Configure it Well

This can be a tricky one for some teams. Many Salesforce developers have become very accustomed to developing using Salesforce’s built in developer console. Perhaps you even count yourself among them. Other developers started using Sublime Text with MavensMate back in the day and have tried to stick with that tool despite its retirement.

Do yourself a favor and make the switch to Visual Studio Code with the official Salesforce extensions. And make sure everyone who writes code for your org does the same. Getting things setup can be a little fussy (pro-tip: choose a JVM that isn’t from Oracle) but you only have to do it once and it’s worth it! Just follow the instructions here or earn this badge on Trailhead.

Why does this matter?

  • Code Formatting / Git Noise: The web-based developer console routinely mangles the indentation of every file it touches. This makes it difficult to maintain consistency and also creates dozens or even hundreds of whitespace changes which add noise to your git repo.
  • Developer Efficiency: VS Code has tons of features which reduce bugs, decrease development time and simplify enforcement of code standards. From multi-cursor to extensive keyboard shortcuts (Windows version here) to code completion to the massive community of extensions, it can save your team multiple dev days worth of time over the course of a year.
  • Standardization: The VS Code extensions for Apex development are developed by Salesforce themselves and are the standard way to build code on the Salesforce platform. By sticking with the standard tools it’s easier to google problems and easier to get community support.

While you’re at it, you should use the features of VS Code to enforce a couple of key standards:

  • Indentation: Pick a standard indentation level & character (2 spaces is most common) and make sure everyone on your team changes their VS Code settings to make this the default.
  • Line Lengths: Pick a standard line length (100 characters is most common) and have your team update their VS Code settings to add a ruler in that column.

Note: There are good use cases for using the developer console, just don’t use it to write production code. Running one off scripts using the Apex code executer in the dev console is perfectly fine (though tools like SoqlX do a better job). The test execution/coverage UI in the dev console is also very handy.

Git-based Development Process

As outlined in the introduction, the beating heart of any scalable software development process is your source control system. Tracking your Apex code in a source-control management system (like GitHub or Blue Canvas) is the first step. But in order to get the operational benefits of a more organized system you’ll need to put your git repository at the center of your team’s workflow.

These Salesforce-development-focused articles from the folks at Blue Canvas are a great starting point:

After that you can search around for other “git workflows” (start with the GitHub flow documentation) and figure out what approach works best for your team. Some of the key questions you’ll need to address are:

  • Will admins use your source control system to push updates or will it just be developers?
  • What kinds of changes will go through code review and which (if any) can be made directly in production?
  • Who will do the actual code reviews? What process should they follow?

Establish a Code Style Guide

Every developer is going to write and format code slightly differently and that’s fine. But some level of coding consistency will help you speed up development and prevent bugs. The easiest way to achieve this is by adopting a code style guide and making sure everyone on your team agrees to follow it.

You could build a style guide yourself if you want, but you’ll save a lot of time if you use an existing style guide as a starting point. The best Apex-specific style guide I’ve found is an open source project started by the team at NimbleAMS. The style guide itself is here: NimbleUser Apex Style Guide. It was adapted from Google's Java Style Guide. For more detail check out Nimble's original 2017 launch post .

Code Structure Overview

Every Apex file in your org should have a single, well-defined responsibility. Your goal when designing a Salesforce code architecture, then, is to build a menu of different Apex file types with clearly defined roles and well thought out design patterns connecting them. Then when it comes time to build a new feature, there’s no new architecture design work to be done. It’s just a matter of picking the right patterns from the menu and filling them in with actual business logic.

There are tons of different options out there when it comes to Salesforce patterns (just google “apex design patterns” or “apex enterprise patterns”). What follows in this section is an outline of some of the most useful patterns I’ve come across. Every org is a bit different, though, so it’s important to think through your particular use case to figure out which patterns are right for your situation.

Code Type Examples Notes
Triggers CaseTriggers.trigger One trigger per object. See: “Apex Trigger Patterns”
Trigger Handlers CaseTriggerHandlers.cls One handler class per object trigger. See: “Apex Trigger Patterns”
Other Handlers EmailHandlers.cls, OpportunityActionHandlers.cls You can use the handler pattern for any type of event handling logic. Remember to keep the business logic out of the handlers themselves. Put the business logic in service classes.

For inbound email handlers, you can create a single handler class with inner classes for each inbound email that needs handling.

For invocable Apex actions, use one action handler per object
Services CaseService.cls One service class per object. See: “Apex Service Classes”
Helpers / Utilities ApiHelpers.cls, DateTimeUtilities.cls One class per functional area, not related to specific objects. See: “Apex Helper/Utility Classes”
Object Libraries CompanyGpsSearch.cls, FinanceObjects.cls, CampaignAnalytics.cls When building out complex business logic, it’s often helpful to create an object-oriented “library class” that encapsulates business logic for a particular topical domain. Library classes should be well named with clearly defined boundaries so they don’t become parking lots for disorganized code.

This type of class is different from a helper/utility/service class because it is object-oriented in its design and usually contains multiple object inner classes with relationships between them.

The inner class objects may map to Salesforce custom objects, or they may map to some other useful abstraction (like days of the week or product categories). The library may also have top-level static methods which return or process instances of the library objects.
Controllers SalesDashboardController.cls, AccountWidgetController.cls One class per Visualforce Page or Lightning Component.

Warning: Avoid using a single controller to power multiple pages or components. Controller-sharing leads to bloated code and makes test classes harder to understand. Instead create one controller for each page or component and use service classes to keep the code DRY.
API Clients GoogleCalendarAPI.cls, GoogleAPI.cls, IntercomAPI.cls Generally, one class per third-party service. If integrating with large multi-API vendors (such as Google or AWS) you may want to have a shared vendor-level API client that handles authentication and sending / parsing requests, then separate API-specific classes.
Mocks GoogleCalendarAPIMock.cls, GoogleAPIMock.cls There are lots of ways to test your API Client code. The most flexible and maintainable option is to create mock classes using the HttpCalloutMock interface.

You should have one mock class for each API Client class.
Custom REST APIs ApiProjectsV1.cls, ApiSupportPortalContactsV2.cls One class per versioned API resource.

Each class implements all of the verbs for a single REST API resource (which ideally maps to a Salesforce standard or custom object).

Be sure to version your API endpoints and classes! Versioning gives you the freedom to deploy breaking changes in the future while maintaining backwards compatibility. So “ApiProjectsV1” would map to a REST path like “/v1/projects/*”.

If you have multiple sets of APIs, you might also want to prefix the names with the name of the API itself. So V2 of the “Support Portal API” might have classes like “ApiSupportPortalContactsV2” and “ApiSupportPortalCasesV2”
Tests CaseTriggerTests.cls, CaseServiceTests.cls, ApiHelperTests.cls, GoogleCalendarAPITests.cls, ApiProjectsV1Tests.cls One test class per non-test class. You may also want some “integration test” classes focused on testing multiple classes at once.

Apex tests can quickly become disorganized so it’s important to have a simple and standardized way to structure them. My favorite strategy is to have a one-to-one model where every class or trigger has exactly one corresponding test class with a matching file name. (Pro-Tip: It’s helpful to think through how your team should pluralize filenames, otherwise you’ll end up with different devs doing it different ways. Ex: “AccountTriggerTests” vs “AccountTriggersTest” vs “AccountTriggerTest” vs “AccountTriggersTests”.)

Be sure to keep the test classes focused only on the code in that specific class as much as possible. This is particularly important/tricky for trigger tests vs trigger handler tests vs service tests.
Batch / Schedulable / Queueable AwsS3Uploader.cls, SalesReportScheduler.cls For completeness I’ll list these here. But classes which implement Batchable, Schedulable and Queueable are pretty easy to structure since they (by definition) come with a predefined interface. I tend to name these using some sort of agent noun, but haven’t come across another more rigid naming convention that makes sense.

A little planning upfront on your design patterns saves a lot of time in the long run.

Useful Apex Design Patterns

As mentioned above there are many many design patterns to choose from when building out an Apex implementation. This section focuses on a few patterns which I’ve found to be indispensable when trying to maintain a scalable codebase.

Apex Trigger Patterns

There are many different schools of thought around how to structure Apex triggers, but most folks agree on a few things.

Only create one trigger per object

Having multiple triggers makes it impossible to control the order of execution, makes it difficult to follow the execution path when debugging, and leads to overall inefficiency. You can read more here.

Keep business logic out of triggers / Use the trigger handler pattern

If you put actual business logic directly into your triggers it makes it impossible to create individual unit tests for each piece of code. This leads to messy, difficult to maintain code. Logic-filled triggers also violate the single responsibility principle and encourage the creation of non-reusable code. This is all solved by the logic-less trigger pattern.

Technically there are many ways you could implement logic-less triggers, but the most popular is to use the trigger handler pattern. This pattern involves turning each trigger into a boilerplate chunk of code which invokes a trigger handler class which, in turn, handles the record filtering logic. The handler class still doesn’t modify any actual database state itself, it just filters records and calls out to helper classes.

Here’s a super simple version of what this looks like in actual code:

This pattern is extremely helpful for writing clean, easy to understand triggers. Many people cite atomic testability as a key advantage of this pattern, which is certainly true. But even more helpful is the clarity that comes by organizing your trigger filtering logic around specific trigger events (before insert, after delete, etc). Having a separate method for each trigger event makes your code way easier to understand and debug.

Some more reading on this topic:

Apex Service Classes

The Service Layer Pattern is a very powerful tool that can help a team create a strong bias towards reusable code. If this concept is new to your team, the Service Layer Badge on Trailhead is a great place to start. The underlying pattern is old and has a long background in many programming languages (including Java).

Once you’re bought into the idea, the implementation is actually pretty simple. The recommended approach is to create a single service class for each Salesforce object for which you have code (“AccountService.cls”, “CampaignMemberService.cls”, etc). Within that class you create static methods which implement different bits of business logic. Your main design constraint is to try and make the service methods themselves as reusable as possible. Keep them generic!

If you find yourself curious about the distinction between a service class and a utility/helper class, read on to the next section.

Apex Utility/Helper Classes

The Helper/Utility Class Pattern is considered by some to be an anti-pattern. But it is none-the-less a common pattern worth mentioning. And for what it’s worth, I think it can be utilized without causing code smell.

From what I’ve observed within the Salesforce development community, the big difference between a Service Class and a Helper/Utility Class is that a Service Class is focused on a single standard or custom object, while a Helper or a Utility is focused on a particular functional area which may be useful to code across many different objects. Some examples are “DateTimeHelpers” for common date and time manipulation methods, “TestUtilities” for reusable test method logic and “ApiHelpers” for methods that help parse, serialize and deserialize API requests & responses.

Of course, the distinction between a Service Class and a Helper/Utility Class is largely semantic and I’ve seen plenty of counter examples which defy simple categorization.

Whatever standard you settle on, the takeaway I would suggest is that it is helpful to distinguish between classes which contain reusable logic for particular Salesforce objects (“Service Classes”) and classes which focus on other non-SObject-based abstractions (“Helper Classes” or “Utility Classes”).

If you are going to have Helper/Utility classes, here are a few tips:

  • Make sure each class is focused on something very specific. JsonHelpers? Great! MiscUtilities? Get ready for a giant mess of unrelated code.
  • Have a single place to document all of the helpers which are available and keep it up to date. Add “documentation updates” as a check during your code review process when Helper/Utility changes are being deployed.
  • Have some sort of process for socializing newly developed helpers (a team lunch and learn, monthly Slack announcements, short video clips to demo each helper, etc). Ultimately your goal is to encourage code reuse and that requires some social engineering.

The “Unified Org Settings” Pattern

The last pattern I’ll mention is one that I haven’t seen anyone develop a catchy name for, so I’ll call it the Unified Org Settings Pattern. This pattern has to do with how we manage constant values in Apex code.

When we write Apex, we frequently need to use values which, though constant for a time, may need to be changed later. Usually these are non-sensitive things like record type ids, default field values, admin email addresses, user/queue ids, API urls, batch process configuration settings, etc. (These can also be sensitive values like API keys, more on that later.)

Here’s a breakdown of the most common solutions to storing and managing Apex constants, from worst to best:

  • Technique 1: Hard-code the value directly in your Apex code. Hopefully this is obvious, but this is a very bad practice because future developers will have to search through the code line-by-line to find all of the various settings available to configure. Burying constant values deep in your code also increases the probability of mismatch errors where a value is changed in one place but not another. Also, this technique is not appropriate for sensitive values like API keys.
  • Technique 2: Create a constant variable at the top of the Apex file. This technique is a massive improvement. By creating a “Settings” section at the top of the file and adding well-named static class variables (along with comments explaining what each variable is for), we’ve made the code much easier to maintain. We still can’t use this technique to store sensitive values. But the biggest issue is that making changes to these constants still requires deploying an update to the underlying code. What if we want non-developers to be able to make these changes themselves? What if we frequently need to change certain settings back and forth between different values in production?
  • Technique 3: Create a custom setting or custom metadata object. Now we’re getting somewhere! By using Custom Settings or Custom Metadata we are creating an administrative interface that allows anyone with appropriate permissions to re-configure the behavior of our code at a later time, plus we can even store sensitive values like API keys. This technique solves many of the problems from technique #2, but it actually creates some new problems which can be tough to get a handle on.

Some common problems with using Salesforce Custom Settings:

  • The big problem with custom settings is that we tend to create different custom settings objects for every new piece of code that gets written. That leads to a proliferation of one-off custom setting objects which are hard to document and manage.
  • Also, by relying on separate custom settings objects for each piece of code we make it harder for developers to utilize this best practice. Creating a new custom settings object and adding it to your code is a 5+ step process. And creating the setting records during Apex test methods adds even more friction. When pressed for time, developers will inevitably take shortcuts and fallback to hard-coded constants, leaving us stuck dealing with the downsides of technique #2.
  • Finally, there are the issues of visibility and discoverability. Technique #2 had its problems, but it was actually pretty great in terms of self-documentation. Having a “Settings” section at the top of each Apex file with clear explanations of each configurable constant is a really great practice. Technique #3 made the settings easier to change, but it actually took us backwards a bit on the visibility front by hiding the various code settings back in the bodies of the class methods (where the custom settings objects are queried).

The Unified Org Settings Pattern

The ideal solution utilizes custom settings but streamlines the developer experience and makes it easier for administrators to manage the settings centrally. This solution also ensures that each Apex file self-documents which custom settings it is using. Here’s how it works:

  • Create a single “Org_Setting__c” custom settings object. It should be list type (rather than hierarchy type). We will use the standard “Name” field as our primary unique identifier of each setting.
  • Add two custom fields to the settings object:
  • Value__c → This is a text field with the maximum allowed length of 255 characters. It will store the setting value. A service class will make it easy to convert the value to other formats (like lists, integers and dates).
  • Notes__c → This is a text area field used to explain to admins how to populate and manage this setting.
  • Pro-Tip:
  • If you want to support values longer than 255 characters, one strategy is to add more value fields (Value_2__c, Value_3__c) and then have them automatically appended together by your service class methods.
  • Create an “OrgSettingService” class with methods which make it easy to save and retrieve setting values with a single line of code. Be sure to include default values so the client code will still work even if the specified setting doesn’t exist.
  • For each Apex file which has settings: Create a “Settings” section at the top with constants for each setting name and default value. Then within the business logic, use the appropriate Org Setting Service method to retrieve the values.
  • In Apex test methods: The Org Setting Helpers can be used to configure settings during test setup.

Here’s some example code to get you started:

The Unified Org Settings pattern reduces the friction needed for developers to implement custom settings in their code. It also makes their code more readable by incorporating a self-documenting “Settings” section at the top of each Apex file. Finally, it streamlines the admin experience by putting all of the various Apex settings into a single unified list.

Conclusion

Salesforce’s Apex is an incredibly powerful tool for enterprise software development. But with great power comes great responsibility. Unscalable Apex code is a real danger, but the patterns and practices above are a great place to start when architecting your Apex codebase for scale and maintainability.

One last piece of advice: The Salesforce development community is wonderful and extremely supportive, but it can also be a bit insular at times. As you work to develop your own set of best practices, don’t be afraid to pull inspiration from other communities and technology stacks. Some of the best Salesforce advice I’ve received has come from experienced web application engineers who’ve never written a line of Apex. Great architecture is universal.

Also, did I forget your favorite Apex pattern? Did I miss something critical? I’d love to hear your tips and tricks for keeping Apex implementations on point. Let me know! I’m @hankish on Twitter.

More like this