SOLID Design Principles In Salesforce Master Class

The Interface Segregation Principle in Apex (SOLID Ep. 5)

4 min read

Subscribe on YouTube

Don’t Make People Implement Things They Don’t Need

The Interface Segregation Principle says:

“No client should be forced to depend on methods it does not use.”

Or: several small focused interfaces beat one big general one.

This is the most immediately practical of the five, because you can spot a violation instantly. If a class implementing your interface has methods with empty bodies or bodies that just throw an exception, congratulations, you’ve found one.


The Kitchen Sink Interface

Apex
public interface DataService
{
    List<SObject> query(Set<Id> recordIds);
    void insertRecords(List<SObject> records);
    void updateRecords(List<SObject> records);
    void deleteRecords(List<SObject> records);
    void exportToCsv(List<SObject> records);
    void sendToExternalSystem(List<SObject> records);
}

Feels tidy. One interface, everything data related in one place.

Now write a class that only reads records for a report:

Apex
public class ReportDataService implements DataService
{
    public List<SObject> query(Set<Id> recordIds)
    {
        return [SELECT Id, Name FROM Account WHERE Id IN :recordIds];
    }

    //And now the sadness begins
    public void insertRecords(List<SObject> records) {}
    public void updateRecords(List<SObject> records) {}
    public void deleteRecords(List<SObject> records) {}
    public void exportToCsv(List<SObject> records) {}
    public void sendToExternalSystem(List<SObject> records) {}

Five empty methods purely to satisfy a contract. That’s not just ugly, it’s actively dangerous, because reportService.deleteRecords(myRecords) compiles fine and silently does nothing.

You might be tempted to throw instead of leaving them empty. That’s worse, and you already know why from the Liskov episode. A subtype that explodes where the contract promised behaviour is a lie.

There’s a third cost too. Add a method to DataService and every implementing class breaks, including all the ones that will never use it.


Segregating It

Apex
public interface RecordReader
{
    List<SObject> query(Set<Id> recordIds);
}

public interface RecordWriter
{
    void insertRecords(List<SObject> records);
    void updateRecords(List<SObject> records);
    void deleteRecords(List<SObject> records);
}

public interface RecordExporter
{
    void exportToCsv(List<SObject> records);
}

public interface ExternalSystemPublisher
{
    void sendToExternalSystem(List<SObject> records);
}

Now the report class implements exactly what it does:

Apex
public class ReportDataService implements RecordReader
{
    public List<SObject> query(Set<Id> recordIds)
    {
        return [SELECT Id, Name FROM Account WHERE Id IN :recordIds];
    }
}

One method. No lies. And a class that genuinely does read and write can implement both:

Apex
public class AccountDataService implements RecordReader, RecordWriter
{
    //...
}

Apex lets a class implement as many interfaces as you like, so you compose the contract you need rather than swallowing one you don’t.


The Payoff Is At The Call Site

Here’s the part people miss. The real benefit isn’t in the implementing classes, it’s in what your methods can now ask for:

Apex
//Signature makes a promise: this cannot possibly modify your data.
public void buildReport(RecordReader reader)
{
    List<SObject> records = reader.query(myIds);
    //...
}

Anyone reading that signature knows, without opening the method, that it can’t delete anything. The type system is documenting the code’s intent for you.

Compare to buildReport(DataService service), where the method is handed the ability to delete records and you just have to hope it doesn’t. Narrow interfaces are a form of safety, not just tidiness.


Salesforce Does This Well, Actually

Worth pointing out that the platform itself is a decent example. Look at the async interfaces:

Apex
public class MyJob implements Queueable, Database.AllowsCallouts
{
    public void execute(QueueableContext context) { ... }
}

Queueable has one method. Database.AllowsCallouts is a marker with none at all. Database.Batchable has its three. Schedulable has one.

Salesforce could have built one AsyncApex interface with nine methods and made everyone stub out the seven they don’t need. They didn’t, and that’s why declaring a Queueable is two lines instead of twenty.


In JavaScript

JavaScript has no interfaces, but the principle applies to what you pass around:

JavaScript
// BAD: hand over the whole object and hope
function renderChart(dataService) {
    const data = dataService.fetchAll();
}

// GOOD: ask for exactly the capability you need
function renderChart(fetchData) {
    const data = fetchData();
}

Passing a single function instead of an entire service object is the same idea. The receiver gets exactly the power it needs and no more, and your Jest tests become trivial because you’re mocking one function rather than a whole object.


Don’t Go Mad

The usual warning. Segregating every interface down to exactly one method is not the goal. If four methods always get implemented together by everything that uses them, they belong together.

The test is simple: is anybody implementing this interface writing an empty method? If nobody is, your interface is the right size. If somebody is, split it along that line.


What’s Next

Interface Segregation: small focused contracts, no empty method bodies, and narrow parameter types that document themselves.

One left. In the final episode we do the Dependency Inversion Principle, which is the one with the biggest practical payoff in Salesforce because it’s what finally makes your code properly testable.

See you next time!


Get Coding With The Force Merch!!

We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.

Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!


Check Out More Coding With The Force Stuff!

If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!

Youtube
Patreon
Github
Facebook
Twitter
Instagram


Salesforce Development Books I Recommend

Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps

Good Non-SF Specific Development Books:

Clean Code
Clean Architecture