Framework-Driven Development

soqsoqsoqlkssooosssqssolqThis post was NOT written by AI.
I only used AI to help refine my grammar, because I am not a native.


Introduction

Software engeneering isn’t simple. Code can become mess without clear way of making decisions. Engeeneries intriduce ways that can guide our work. Probably you’ve heard about some of them, like:

… and more.

All of them are great when used correctly and can ensure code quality. However in AI-era, the new ways can be introduced. One of my collegues Constanting Schlusche introduced some time ago a new concept called Context-Driven Delivery what perfectly solve some problems that vibe coding and AI creates.

Today, on top of existing strategies, I would like to introduced a new one called – Framework-Driven Development.

This is the concept that we were developing in Apex Fluently for the last couple of months.

Motivation

AI feels like magic – that’s true. You can genereate code faster that ever before and what’s even more interesting – you can generate that code without understanding it at all – you know – vibe coding. Additionaly LLMs entairly based on statistics and probability and because it was train on the massive amout of code in the internet – the AI’s output is at best average.

That comes with big price – code quality. I don’t want to explain why it’s important and what can be concequences, let’s just assumpe that – code quality it is important.
On platform like Salesforce – when we have the limits, limits of SOQLs, DMLs, CPU Time, Heap Size and so one, architecture should be thoughtful. We shoudn’t just ship code faster and faster because we got tools that can do that.

Today I encounted many situation when developers are a "meat proxy" and throw "slop grenade". If you didn’t heard about it, a few definitions below:

So if developers don’t understand the code, take shortcuts to ship faster (and it’s not their fault as it’s generall pressure to be more productive) and the ownership area is gray – you don’t own the code as you use to. We can ended up in very bad place.

I don’t want to change the world and I don’t have power to do that, but I do what I can to fight for code quality and thoughtful solutions – as I still believe that software engeenering is kind of art.

Assuming that nothing will change in the future and we will vibe code more and more we need some kind of gurdians, gurdians of quality!

Each provider (Codex, Claud Code, Copilot, etc) provided use set of tools that can help LLMs generated better output: agents, skills, rules, master propmpts.
Those are great tools and we should definetely use them, but event with the best skills and rules – not all problems are solved.

In Framework-Driven Development I would like to introduce new level that foce code quality – FRAMEWORKS and LIBS.

What is Framework-Driven Development?

Framework-Driven Development is a concept where development is driven by frameworks (you don’t say sherlock).

What does it mean?

Framworks introduce set of rules and design principles that cannot be skipped either by developers, either by AI Agenets. Each Lib has kind of contract (usually interfaces and public methods) that can be used in order to create new code. There is no way around, you/AI Agent use only the methods exposed by contract.

For instance DML Lib:

  public interface Commitable {
        // Insert
        Commitable toInsert(SObject record);
        Commitable toInsert(DML.Record record);
        Commitable toInsert(List<SObject> records);
        Commitable toInsert(DML.Records records);
        // Update
        Commitable toUpdate(SObject record);
        Commitable toUpdate(DML.Record record);
        Commitable toUpdate(List<SObject> records);
        Commitable toUpdate(DML.Records records);
        // Upsert
        Commitable toUpsert(SObject record);
        Commitable toUpsert(SObject record, SObjectField externalIdField);
        Commitable toUpsert(DML.Record record);
        Commitable toUpsert(List<SObject> records);
        Commitable toUpsert(List<SObject> records, SObjectField externalIdField);
        Commitable toUpsert(DML.Records records);
        // Delete
        Commitable toDelete(Id recordId);
        Commitable toDelete(SObject record);
        Commitable toDelete(Iterable<Id> recordIds);
        Commitable toDelete(List<SObject> records);
        // Hard Delete
        Commitable toHardDelete(Id recordId);
        Commitable toHardDelete(SObject record);
        Commitable toHardDelete(Iterable<Id> recordIds);
        Commitable toHardDelete(List<SObject> records);
        // Undelete
        Commitable toUndelete(Id recordId);
        Commitable toUndelete(SObject record);
        Commitable toUndelete(Iterable<Id> recordIds);
        Commitable toUndelete(List<SObject> records);
        // Merge
        Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord);
        Commitable toMerge(SObject mergeToRecord, List<SObject> duplicateRecords);
        Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId);
        Commitable toMerge(SObject mergeToRecord, Iterable<Id> duplicatedRecordIds);
        // Platform Event
        Commitable toPublish(SObject record);
        Commitable toPublish(List<SObject> records);
        // Immediate Insert
        OperationResult insertImmediately(SObject record);
        OperationResult insertImmediately(DML.Record record);
        OperationResult insertImmediately(List<SObject> records);
        OperationResult insertImmediately(DML.Records records);
        // Immediate Update
        OperationResult updateImmediately(SObject record);
        OperationResult updateImmediately(DML.Record record);
        OperationResult updateImmediately(List<SObject> records);
        OperationResult updateImmediately(DML.Records records);
        // Immediate Upsert
        OperationResult upsertImmediately(SObject record);
        OperationResult upsertImmediately(DML.Record record);
        OperationResult upsertImmediately(List<SObject> records);
        OperationResult upsertImmediately(DML.Records records);
        // Immediate Delete
        OperationResult deleteImmediately(Id recordId);
        OperationResult deleteImmediately(SObject record);
        OperationResult deleteImmediately(Iterable<Id> recordIds);
        OperationResult deleteImmediately(List<SObject> records);
        // Immediate Undelete
        OperationResult undeleteImmediately(Id recordId);
        OperationResult undeleteImmediately(SObject record);
        OperationResult undeleteImmediately(Iterable<Id> recordIds);
        OperationResult undeleteImmediately(List<SObject> records);
        // Immediate Publish
        OperationResult publishImmediately(SObject record);
        OperationResult publishImmediately(List<SObject> records);
        // Mocking
        Commitable identifier(String dmlIdentifier); // used for mocking and tracking results
        // Debug
        void preview();
        // Field Level Security
        Commitable userMode();
        Commitable systemMode();
        Commitable accessMode(System.AccessLevel accessMode);
        // Sharing Mode
        Commitable withSharing();
        Commitable withoutSharing();
        // Other configs
        Commitable allowPartialSuccess();
        Commitable skipDuplicateRules();
        Commitable includeOperationIdInErrorMessage();
        Commitable options(Database.DmlOptions options);
        Commitable discardWork();
        Commitable commitHook(DML.Hook callback);
        Commitable combineOnDuplicate();
        // Save
        Result dryRun();
        Result commitWork();
        Result commitTransaction(); // make a savepoint and rollback on exception
    }

Additionaly the framework handles the whole code optimalization and reduce number of SOQLs, DMLs and CPU Time to avoid hitting the limits.

The contract should be well designed and prevent from making logical mistakes. It should guide developer and AI Agent and automatically give a feedback about the problems.

That was high-picture overview – let’s go into details.

Code Optimization

As I already mentioned – in vibe code era less and less developers know anything about code architecture, best practice and optimalization. We should move at least part of that responsibilty to the development process. A process that relay on frameworks can be much more efiicient and better optimize. The libs have inner optimalization that won’t allow to exceed Salesforce limits.

Async Lib

The best example here would be Async Lib, which fully orchestrate async jobs (queueable, schedulable, batchable) in Apex.

One of the biggest issue, which is not that easy to solve when dealing with async is "Too many queueable jobs" issue. The issue can appear out of nowhere.

Example from stackexchange:

So if you update 201 records, you will have one Queueable kicked off with 200 records, and another kicked off with just one record. As soon as you execute a second job, you’re going to error out. ~ https://salesforce.stackexchange.com/questions/207441/error-too-many-queueable-jobs-added-to-the-queue-2

So everything can looks good, but one they you have 201 records in one transaction and facing "Too many queueable jobs". There is more scenarious when you can encaunter it. Usually it requires proper synchronization of processes or weird if statements:

if (System.isBatch() || System.isFuture() || System.isQueueable() || System.isScheduled()) {
   return;
}

// logic here..

Async Lib take out that responsibility from developers/AI Agents and automatically handle it.

DML Lib

Another great example can be DML Lib that minimize number of DMLs by creating dependency graph and resolving it using Kahn’s algorithm.

It doesn’t matter how developer or AI Agent will register records for CRUD operation, the lib itself will optimize it. They don’t need to be awere of other processes/classes that may add records to UOW instance. In this way we mitigate Human/AI errors by moving the responsibility to the lib.

SOQL Lib

How to reduce number of SOQL Queries?

You can use SOQL Lib with cache module.

The lib covers the whole caching process, the only thing the developers have to do – is decide which object are worth to cache.

You can easily configure if data should be stored in Apex static variable (. cacheInApexTransaction()), org cache (.cacheInOrgCache()) or session cache (. cacheInSessionCache()). Additioanly you can easly configure how long data should stay in cache with the maxHoursWithoutRefresh(hours) method.

public inherited sharing class SOQL_CachedProfile extends SOQLCache implements SOQLCache.Selector {
    public static SOQL_CachedProfile query() {
        return new SOQL_CachedProfile();
    }

    private SOQL_CachedProfile() {
        super(Profile.SObjectType);
        // default settings
        with(Profile.Id, Profile.Name, Profile.UserType)
            .cacheInOrgCache()
            .maxHoursWithoutRefresh(36);
    }

    public SOQL_CachedProfile byName(String profileName) {
        whereEqual(Profile.Name, profileName);
        return this;
    }
}

Trigger Lib

Trigger Lib allows to reduce number of parent queries by optimazing it.

public with sharing class AccountParentDefaultsPopulator implements BeforeInsert.Populator, BeforeInsert.NewRecordEnrichment {
    public Map<SObjectField, TriggerHandler.FieldSelection> newFieldsToEnrichOnBeforeInsert() {
        return new Map<SObjectField, TriggerHandler.FieldSelection>{ Account.ParentId => TriggerHandler.FieldSelection.with(Account.Industry, Account.AccountSource) };
    }

    public Boolean populateOnBeforeInsertWhen(TriggerHandler.InsertRecord record) {
        return record.isNotNull(Account.ParentId);
    }

    public void populateOnBeforeInsert(TriggerHandler.InsertRecord record) {
        Account parent = (Account) record.getNewRelated('Parent');
       // ...
    }
}

Auto Feedback

Code Quality

Summary

Piotr Gajek
Piotr Gajek
Senior Salesforce Developer
Technical Architect and Full-stack Salesforce Developer. He started his adventure with Salesforce in 2017. Clean code lover and thoughtful solutions enthusiast.

You might also like

Abstract, Virtual, Interface in Apex
August 14, 2022

Abstract, Virtual, Interface in Apex

Find the differences between Abstract, Virtual, and Interface implementation in Apex code. Understand the purpose and make your code better.

Piotr Gajek
Piotr Gajek

Senior Salesforce Developer

SOQL Lib

SOQL Lib

The SOQL Lib provides functional constructs for SOQL queries in Apex. Use the SOQL Lib as a Selector Layer on your project.

Piotr Gajek
Piotr Gajek

Senior Salesforce Developer