{{announcement.body}}
{{announcement.title}}
refcard cover
Refcard #358

Salesforce Application Design

Surfing the No-to-Full Code Spectrum

Since 2008, Salesforce has allowed developers worldwide to build and manage a custom CRM for their enterprise. This Refcard provides an overview of how to design applications to run on the Salesforce Platform, exploring the no- to-full code spectrum.

Free PDF for Easy Reference

Brought to You By

Skuid
refcard cover

Written By

author avatar John Esposito
Technical Architect, 6st Technologies
Section 1

Introduction

In 2008, Salesforce allowed developers to build on the platform that they had used to build the SaaS CRM — now a full-fledged PaaS known as Salesforce Platform. This Refcard provides an overview of how to design applications to run on this PaaS.  

Section 2

Salesforce as PaaS

Salesforce is known primarily as SaaS but owes much of its success to its PaaS capabilities. The major advantages of the Salesforce Platform PaaS are:  

Fully managed infrastructure  

All the way from dev to prod, there is no local Salesforce runtime. All code runs on Salesforce servers (called orgs), from individual development environments to production. Production orgs run faster than pre-production orgs (called “sandboxes”), and sandbox orgs get platform-level updates monthly before production orgs.   

To ensure backwards compatibility, every test written by every Salesforce user is run on every platform release candidate, using a tool Salesforce calls the “Apex Hammer,” and results are compared with the same tests run on the current platform. Breaking changes are extremely rare, but if they do affect your code, then you can just change one setting and Salesforce will run your code on an older platform API version.  

Apex: A Java-esque server-side language  

If you can write Java, then you can write Apex. In fact, unofficially, Apex is built on top of Java (this is visible in stack traces), so not only syntax but also some logic and performance quirks apply.   

Anecdotally, a decent number of Salesforce developers are former Java and/or C# developers who ended up in the Salesforce ecosystem because they could already write Apex and didn’t want to worry about ops (including local dev environment setup/config).  

Point-and-click database management  

You can create new tables (called SObjects — these are actually richer than tables, so the Salesforce database is technically an Object-Oriented DBMS, though unofficially it is built on top of Oracle) via point and click in the Salesforce SaaS admin or via the metadata API.   

SObjects can have a variety of column types, with referential integrity enforced (via lookup or master-detail relationships defined between SObjects). Indexes can be customized (some via point-and-click, some via ticket with Salesforce support).   

SObjects are queried via a SQL-like language (called SOQL, the Salesforce Object Query Language), searched (with Solr-like logic) via a language called SOSL (the Salesforce Object Search Language), and can easily handle millions of rows (with larger options available).  

Mature low-code automation tools   

At the time, Salesforce actually exposed fairly robust point-and-click workflow automation tools before they exposed the full PaaS to developers. These tools are getting more powerful — and the line between “code” and “no code” applications blurrier, especially with the recent introduction of Flows — over the last two decades.   

So developers have less boring code to write. Salesforce also has certifications aimed at leveling up admins into low-code developers, which helps avoid point-and-click spaghetti (although, this is still an issue, which is discussed below).  

Excellent availability  

Salesforce seems to have cracked the large-scale consumer-facing cloud nut, so they know what they’re doing. All Salesforce orgs are multi-tenant — which of course has cons (discussed below), but one of the pros is that if your infrastructure is down, then a bunch of other organizations’ infrastructure is down too, so Salesforce is incentivized to address the issue quickly.  

Robust, highly configurable security model  

SObject-, field-, and record-level access to all data can be controlled via point-and-click admin UI. Security can also be manipulated programmatically (via Apex managed sharingwhich basically means just inserting special records into special tables) where more dynamic and granular control is needed. Sharing can be tuned for individual classes via special class-level keywords: with sharingwithout sharing, and inherited sharing 

Large integration ecosystem  

Because Salesforce has been around (as a CRM) a long time, many large companies use   

Salesforce SObjects as a source of truth for certain essential kinds of customer data. The SaaS CRM sits between customer-facing activities (handled by salespeople upstream) and production activities (handled by ERP systems downstream). So the PaaS that supports this SaaS is located smack dab in the middle of many companies’ workflows.   

All large middleware integration platforms, and many of the smaller ones, are already set up to handle the SOAP API and REST API that Salesforce provides for basic manipulation of SObjects. Many Salesforce apps include ancillary point-and-click integrations out of the box. It’s also easy to create your own custom REST services (basically just add some annotations to your class) in a Salesforce org.   

Large, mature business app ecosystem  

The Salesforce AppExchange is the largest business app store in the world. Many common business automation problems have already been solved by an AppExchange app. Many common administrative tasks have also been further automated on the AppExchange. If a task doesn’t require complex domain-specific logic, then there’s a good chance something relevant is available on the AppExchange. This reduces the repetitiveness common in enterprise CRUD and reserves time and attention for truly use-case-specific development. 

Database Schema 

You can think of the Salesforce platform as nothing more than a database (SObjects) with a server-side scripting language (Apex), a number of front-end runtime options (Visualforce, Lightning), and a robust set of application services.  

Schemas are flexible, but joins (except for semi-joins) must be defined before query time. Two kinds of relationships are supported: 

Type 

Definition 

Example 

Lookup 

Foreign key. Can be optional. SObject type has meaning without populated FK. 

Contact.AccountId — the concept “Contact” does not necessarily entail the concept “Account. 

Master  

Detail 

Foreign key. Always required. SObject type has no meaning without populated FK. Detail inherits sharing from master. All detail records are deleted when the master is deleted. 

OrderItem.OrderId — there is no such thing as an OrderItem without an Order. If an Order disappears, then all OrderItems should disappear too. 

 Some fields in standard SObjects (i.e., those provided by Salesforce) can have polymorphic relationship fields (i.e., can be related to >1 SObject type). But all relationship fields in custom SObjects must be defined in relation to one SObject type. The result of the lack of on-the-fly joins on arbitrary fields is that the database itself encourages you to model data in at least third normal form (3NF).  

In practice, Salesforce developers will almost always deal at least a little with the original Salesforce DNA: a SaaS CRM. So, you should know at least the basics of the core Salesforce CRM data model.  

Core Salesforce CRM Data Model 

SObject 

Description 

Account  

Stores data about customer organizations. Usually represented as a tree (using Account1.ParentId==Account2.Id). 

Contact  

Stores data about individual humans that have a relationship with the organization.  Almost always related to an Account (Contact.AccountId==Account.Id).  Sometimes related to a User (User.ContactId==Contact.Idfor customer-facing applications. Sometimes “converted from” a lead (Lead.ConvertedContactId==Contact.Id) 

Lead 

Stores data about individual humans that are not yet considered Contacts. Usually less structured than Contact data, with fewer integrity checks. The CRM has many automations built on top of Leads. 

Opportunity  

Stores data about individual items in a sales pipeline. A good example of an SObject whose records have a richly defined state, with “wizard” UI that SF users come to expect. 

Case 

Stores data about non-sales-pipeline items for which state and high touch are crucial.  Originally for support cases (hence the name) but often used for any stateful data that users need to be aware of in a timely manner. Can easily be auto-assigned to individual users or sets of users (in a queue) using point-and-click assignment rules. 

Many more SObjects are included with Salesforce out of the box, depending on your subscription. Some were added by Salesforce organically; others were added via acquisitions, with varying levels of absorption success. Still, other acquisitions are now included in Salesforce products but not located on the platform (either database or runtime). Orgs can generate their own ER diagrams dynamically, but for a good overview of the standard SObject, see https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/data_model.htm .

Apex Features

Apex is Salesforce’s proprietary server-side language. Think of it as an older version of Java, plus some Oracle DML (data manipulation language) that makes basic data persistence trivial. Database triggers are also written in Apex and are used very often. The following table includes a small subset of Apex language features selected to give you a basic feel for the language. 

Language 

Feature 

Specifications 

Description 

Primitive Types  

Each is also represented as a class with utility methods. 

String, Decimal, Integer, Long, Double, Boolean, Date, Datetime, Blob 

Collections  

 

List (ordered with index), Set (unique, not ordered, no index), Map (key-value pairs) 

DML Operations  

 

insert, update, delete, upsert (update if exists, insert if not exists), merge (for quick de-duping), undelete 

Standard   

Annotations  

Common. 

@IsTest (designates a class that is a unit test, which SF will run in a sandboxed copy of the database), @RemoteAction (quick way to make a server-side method securely available to browser JavaScript), @AuraEnabled (similar but specifically for Lightning client-side code), @RestResource (makes class available via REST endpoint), @InvocableMethod (makes method invocable by point-and-click Process Builder and Flow automations) 

Trigger Context Variables  

Populated in Apex triggers only. 

isInsertisUpdateisDeleteisBeforeisAfter,  isUndeletenewoldnewMap (Map<Id,SObject{new state}> in after triggers only), oldMap  (Map<Id,SObject{previous state}) 

SOQL Examples  

SOQL is the SQL-like language you use to query SObjects. Think in terms of an Object-Oriented DBMS plus relational normalization, or an ORM. Syntax and keywords resemble SQL, so the following examples are designed to showcase SOQL-specific ORM-y features.  

SQL
 
SELECT 
     Id, //primary key; first three characters indicate the SObject type 
     Name, 
     ParentId, //foreign key, defined as a ‘lookup’ field to Account 
     Parent.Name, //FK relationship traversed inline; table join handled automatically 
     ShippingAddress, //field of type System.Address 
     ShippingState, //property on ShippingAddress, accessible directly 
( //subquery 
select Id, 
     Name, 
     Email 
from Contacts //name of relationship established by Contact.AccountId 
) 
FROM Account 
where ParentId in (select Id from Account where ShippingCity = ‘New York’); //semi-join: return only Accounts whose parent Account is located in New York 

 

SOQL queries can be assigned to Apex variables representing List collections of the SObject type:  

SQL
 
List<Account> accounts = [select Id,...]; 
Lists of SObjects are often stored as Map<Id,SObject> using the   
Map<Id,SObject>(List<SObject>) constructor, often inline with a SOQL query:  

Map<Id,Account> accounts = new Map<Id,Account>([select Id,...]); 

 

This syntax automatically assigns the Map key as the primary key of the List (i.e., the Id field). Subqueries are accessible in Apex as collections (type List) by traversing the relationship name: 

SQL
 
List<Account> accounts = [select Id,...]; 

List<Contact> relatedContacts = accounts[0].Contacts; //’Contacts’ is the relationship name, as in SOQL query above  


Typed fields like ShippingAddress are accessible in Apex from the SOQL result set like any other Apex type:  

SQL
 
Account acc = [select Id, Name, ShippingAddress from Account where ShippingState != null limit 1]; 
System.Address someAddr = acc.ShippingAddress; 
System.debug(someAddr.getStateCode()); //IL 


Section 3

Salesforce-Specific Expectations

Any ecosystem has its quirks in both technical and business respects. Here are some that are specific to Salesforce:  

1. Application development should be super-fast.  

People pay a lot for Salesforce. No other CRM offers nearly as much declarative automation or nearly as robust a PaaS. “If an admin can create complex automations via clicks, then surely a developer can implement any requirements in basically no time!”  

2. Adoption is often a work in progress.  

Salesforce promises a ‘”360-degree view of the customer,” which requires a lot of inputs from a lot of workers, often poorly trained.  

3. Data is often bad.  

This is very much true for Salesforce due to multiple reasons. Iffy adoption means lazy data population and integrity maintenance (“We bought Salesforce, so you have to use it! And also, these fields are required!” “Fine, here’s some crap data so I can save this record and keep selling stuff.”).   

Salesforce often sits between many systems and has an easy-to-modify schema, so it often follows fewer flexible systems – not always consistently. Standard storage levels come in large chunks (though more can be purchased), so people get sloppy about cleanup.  

4. The whole org runs like one big application, but people may not think of it that way.  

Everything running in a Salesforce org shares a database and runtime, and the platform offers developers limited transaction management. So an automation that an admin built in three clicks, without any automated unit testing, might easily interact with seemingly unrelated code (e.g., at the database trigger level), and much of the OOTB declarative automation provides very little logging.  

5. Salesforce UX design can be weirdly coupled across apps built on the platform.  

Salesforce offers a standard CSS framework (SLDS — the Salesforce Lightning Design System) and, more recently, a client-side stack based on the Web Components standard (LWC — Lightning Web Components).   

So users expect a component-based page architecture with a very specific look and feel. These expectations may not suit your application’s workflows very well. So as an app developer, you must “start with the user” even more deliberately and carefully for Salesforce apps than for other apps.  

Section 4

Salesforce Application Development Spectrum

In the following sections, we consider four levels of Salesforce application complexity. This is not intended as a comprehensive reference, but rather as a just-granular-enough overview of what kinds of problems you can solve with which sort of Salesforce SaaS or PaaS tooling to help you sketch out your application design.  

Level 1: Simple Automation 

Here are four commonly used automation tools available on the Salesforce platform that require no coding ability. Most of this logic can be implemented without any code at all (not even Excel-like formulas), or by using point-and-click tools to simplify formula creation.

Tool 

Specifications 

Example 

Formula   

Field 

Can be added to any SObject. Available in SOQL as a simple field name. 

IF(ISBLANK(FirstName),LastName,LastName & ',' &  FirstName) (if no FirstName then show LastName; otherwise show LastName, FirstName) 

Roll-Up   

Summary   

Field  

Can be added to the Master SObject on fields in any Detail SObject. 

<summaryForeignKey>OpportunityLineIt em.OpportunityId</summaryForeignKey>    

<summaryOperation>count</summaryOperation>  

(Count of all OpportunityLineItems that are Detail records on this Opportunity Master record) 

Validation Rule  

Can absolutely block or merely warn on failed validation. 

FirstName='Darth' && LastName='Vader' (fail validation if full name is Darth Vader) 

Workflow Rule  

Conditions evaluated on create or update. Can trigger field update, email, task creation, approval process, and/or SOAP message. 

If LastName == ‘Fett’ then:  

• Update Profession to ‘Bounty Hunter’  

• Send email to lando@cloudcity.net  

• Create a reminder not to warn Han in 10 days 

Level 2: Complex, No-Code Applications 

Salesforce offers two additional no-code tools that can implement relatively sophisticated control flows without writing any code. Process Builder is the older technology; think of it like a more powerful version of Workflow Rules. Flows (formerly known as Lightning Flows, an evolution of Visual Flows) are more recent, far more powerful, and should be considered a visual programming language.  

Tool 

Specifications 

Process Builder  

More flexible than Workflow Rules, but no longer being developed. 

Flow  

Suitable for complex CRUD, state-based UI flows that can embed LWC. Intended to replace the Process Builder. 

In many cases, it’s obvious when to use Flows vs. Process Builders; and in many cases, it isn’t. This is an effect of Salesforce’s rapid platform development (product/feature overlaps are not always clear) and often turns into a bit of design-and-technical-debt headache, especially in consulting jobs. 

Level 3: Clicks Plus Light Code

Process Builder and Flow can do a lot but are often “gateway drugs” to code. The first step in the slippery slope from Salesforce admin to Salesforce developer is often one of the following three:  

Tool  

Specifications  

Apex Trigger 

Written in Apex.   

Normally used to apply simple checks before passing on to classes that implement actual business logic. Often a gateway from admin to developer roles. 

Process Builder Calling Invocable Apex 

Same as no-code   

Process Builder but passes variables onto Apex methods marked as @InvocableMethod 

Flow Calling Invocable Apex  

Same but for Flows. 

  

Clicks Not Code: Pros and Cons 

Visual programming can be extremely powerful, and Flows are lightyears ahead of any previous point and-click automation tool available OOTB on the Salesforce platform. But every solution involves tradeoffs. Here are some ways to consider pros and cons of building Salesforce apps using “clicks not code”:  

Benefits of “Clicks Not Code”
 

1. Nobody knows what they want.   

Requirements are typically even less clear or understood up front than for non-Salesforce apps because Salesforce apps are “sub-apps” built into complex org-wide user workflows.   

Often many iterations are needed before users can even tell whether their pain points are being addressed. Declarative solutions make simple changes trivial. Also, they do not require code deployments.  

2. No need to handle the boring stuff.  

Many automations of business processes are simple and similar enough that the reward of solving the problem in code is small compared to the effort, but the benefit to the business is considerable. The more such problems are solved without code, the less time you waste writing boring, repetitive boilerplate with a little business logic sprinkled in.  

3. Poor adoption of version 1.  

Even slightly inconvenient user flows are likely to lead to poor adoption of any custom solution.  OOTB Salesforce adoption is difficult enough (users often input bad data, don’t keep records up to date, etc.).   

Because Salesforce’s no/low-code tools are so powerful, many business process improvements can be facilitated by small changes — so up-front design is unlikely to seem worth the costs. But this means that the first version is highly likely to be wrong, which makes the ability to iterate quickly even more important than usual.  

4. The floodgates open.  

Once you build a custom app, users may expect many changes quickly “because building SF apps is easy.” Small applications are often not worth writing in code because they do not need a Turing-complete language.  

5. All code is (potentially) technical debt.  

Code rots like fish, not wine. As a rule, the less code the better (for business purposes at least).  

Challenges of Salesforce-Native “Clicks Not Code” 

1. Impedance mismatch.   

Flows are good for CRUD but not complex data transformations. If your code is naturally suited to a bunch of if-thens, for loops, and simple state machines, then declarative solutions are probably best. If not, then declarative solutions suffer significant impedance mismatch. And even if so, lots of ifs are horrible to reason about, cannot easily be understood at a glance, and realistically cannot be maintained without extensive automated testing (due to high cyclomatic complexity) — which Salesforce does not require of Flows. 

2. Poor complexity scaling  

Complex business logic that can technically be implemented in Flows is often hard to read/arrange visually. Flow definitions are not automatically expressed in a visually meaningful way. Flows do offer auto-arrange, but this is a poor, domain-ignorant guess. Icons in flows do not express much visual hierarchy, so a truly complex flow can be much harder to read than identically complex code.  

3. Limited UI.  

UI elements available declaratively without custom code are limited. Salesforce is always adding more, but many use-cases that Flows are suitable for at the level of control flow (e.g., wizards) require user interaction in each state that is not available via clicks only. So the Flow can turn into nothing but glue-sticking custom code (LWC) together. This may or not add much value.  

4. Little structure among different Flows.  

Each Flow can call a Subflow, but there is no clear way to distinguish basic, everyday admin tasks from deeper business logic definitions. Imagine a class structure as a linked list: One class may point to another, but no richer algebra is available. Or imagine an application consisting solely of static methods. The result is that centrality of each node is extrinsic to the design, determined entirely by a specific call chain.  

5. Primitive debugging.   

Errors thrown by Flows are often cryptically messaged or silently swallowed. This does force you into aggressively deliberate error handling (Flows allow you to model error states easily), but if you miss anything, then the resulting logs are comparatively information-sparse.  

6. Not intimately linked with unit testing.   

Salesforce requires 75% coverage of all custom Apex run in production but imposes no such requirement on point-and-click automations. Unit tests can (and should) be written for all low code logic, but flipping back and forth between declarative visualizations (or metadata XML) and/or custom Apex is an expensive context switch.  

Level 3.5: App Builder Ecosystem

Sometimes, a problem is too complex for Flows but maybe not important enough to justify full-code development. Because such situations are common in enterprise contexts — and because the “Salesforce-y” thing to do is to empower admin clicks as much as possible — a robust ecosystem of third-party rapid application development tools has grown up on the Salesforce PaaS. Here are some scenarios that might suit such tools: 

  1. Complex or finicky UX demands. 
    The Salesforce front-end stack comes with powerful CSS frameworks out of the box, but the standard SLDS classes are optimized for a particular (whitespace-heavy, mid-2000s, record-with-related-records) look and feel. If your app requires more complex UX, you may be looking at adding another framework on top of the OOTB imports or writing a lot of UX boilerplate yourself. 

  1. Business logic that involves many tables and/or external systems. 
    A lot of enterprise CRUD is a lot simpler at a high level than a low level. Third normal form is efficient and clean, but 3NF ER diagrams can be cluttered, hard to read, and hard to maintain, especially for admins that are not RDBMS specialists (as Salesforce admins typically aren’t). When your ER and BPM diagrams start looking painful, model-driven tools might make your life easier. 

  1. Strong branding requirements. 
    To a developer, branding can seem like unimportant window-dressing. But companies spend many millions keeping everything — even internal tools — perfectly on-brand. If branding requirements are granular and dynamic, you might spend a lot of time restructuring LWC shadow DOMs and messing with border-radius values.

Level 4: Full-Code Applications 

As a developer, you probably want to write code. Before writing code for the Salesforce PaaS, though, consider the following benefits and drawbacks of the platform from a coder’s point of view.  

Benefits of Full-Code Salesforce Applications
 

1. No local environment setup.  

The Salesforce PaaS is completely self-contained. No runtime resources are available to you anyway. Salesforce allows you to edit any code in-browser, so you don’t even need a local IDE, text editor, or connection to a Salesforce server.   

The in-browser editors do not have any version control, however, so you’ll want a local text editor/IDE for any serious development. But it’s very convenient to be able to add small changes to play with a specific environment without any setup at all, especially for debugging release pipelines.  

2. No low-level ops.  

You can’t control anything at the VM level, so you don’t have to worry about it. You can’t control much beyond the Apex runtime, so no higher-level infrastructure (container orchestration, load balancing, etc.) is your problem. If Salesforce servers fail, then all the blame lies with Salesforce, and everyone understands that.  

3. Trivially easy database changes. 

Not only can you add an SObject (similar to a table) or field in a few clicks; but you can also do so by editing the XML files that define SObjects locally, then deploying to the org. Also, because all schema definitions are available locally, they can be deployed automatically (with suitable linting, tests, whatever) in the same package as your application code.  

Challenges With Full-Code Salesforce Applications
 

1. Variable multi-tenant performance.  

All Salesforce orgs are multi-tenant, so you will have noisy neighbors. Salesforce imposes strict platform-level limits on resource usage, but this doesn’t solve the problem completely.  

2. Platform limits.  

To address the noisy neighbors problem (and presumably to limit their own expenses, since Salesforce subscriptions are not granular to compute resource usage), Salesforce halts execution whenever certain resource usage limits (called governor limits) are reached. These exceptions end the transaction instantly and cannot be caught. Some apply per Apex transaction; others apply per time period or at any given moment in time.  

a) Per-transaction limits include: hard caps on CPU time, heap size, query count, query rows returned, DML statements issued, stack depth, http callouts, asynchronous method invocations, asynchronous jobs queued, emails sent, absolute execution time, push notification methods called, and events published to the platform event message bus.  

b) Non-per-transaction limits include: asynchronous methods invoked per 24-hour, long-running synchronous transactions concurrent across the entire org, overlapping scheduled jobs, queued batch jobs, concurrent batch starts, test class runs per 24 hours, and concurrently open query cursors.  

3. Outdated, complex stack.   

a) The Apex server-side language and runtime are proprietary and closed-source. However, at a basic level, they are similar enough to Java/JRE and C#/CLR that many developers do not face a major learning curve as they begin to use Apex.  

b) The original web UI framework (VisualForce) is proprietary (based on JSP), server-side, and clunky. However, Salesforce now discourages VisualForce development.  

c) The first client-side web framework (Aura) is proprietary, verbose, and odd — more declarative than is natural for JavaScript. However, Salesforce now encourages developers to use Lightning Web Components (LWC) for client-side development, which is based on the W3C Web Components standard.  

d) The new client-side web framework (Lightning Web Components) is standards-based but still quirky and heavyweight. Even the W3C’s Web Components standard has not proved as popular as modern, non-W3C-standard JS frameworks (like React and Vue) partly for the reason that all generally reusable modules eventually prove more or less intractable: It is hard to strike a balance between power and complexity of configuration. 

e) Much of the stack is just old. Apex resembles Java from 10 or 15 years ago. Aura resembles JSP translated to the client. This drawback does not strictly apply to LWC, since Web Components are a relatively new standard, but even the Web Components standard is heavyweight (and insofar “feels old”) compared with modern frameworks like Vue.js.  

4. Poor debugging experience. 

Server-side debugging is extremely limited and costs a large additional subscription fee. Since no local runtime is available, it is impossible to set Apex breakpoints locally. However, log analysis tools, integrated with IDEs and increasingly powerful in recent years, have made pseudo breakpoint local debugging possible (although for complex transactions this quickly runs into Salesforce’s hard log size limits). But this is still far worse than a modern Java debugging experience, or even than Visual Basic debugging in the 90s.  

5. Immature ecosystem.  

a) Many unskilled developers. This is partly an inevitable result of the excellent free training materials that Salesforce makes available. These materials, along with the platform’s powerful low-code tools, make the transition from end-user to administrator to developer extremely easy. This is good insofar as it empowers people to ride bicycles of the mind, but bad insofar as the ecosystem-level filter that blocks poor developers from spoiling codebases is extremely permissive.  

b) Who knows what automation (code or not) will have what effect in any given org? Salesforce-optimized IDEs are not yet powerful enough to simulate execution graphs that include Salesforce’s declarative automation tools. So when values change unexpectedly, it’s hard to find all possible mutators by poking around your codebase directly.  

The org might contain some Flow that might not even be in your source, because some admin clicked and dragged a few times. This can lead to extremely frustrating debugging, especially in orgs that have been around for a while — as if you were debugging an application that random developers, some with no coding experience, had built over a few decades with no plan or design thought.  

c) Many hacky, rushed projects.  

Section 5

Sample Architecture

To get a feel for modern Salesforce application architecture (using Lightning Web Components), consider the following simple application: an e-commerce cart with the ability to process promotions (i.e., discounts that follow potentially complex business rules).

Image source: https://lucid.app/publicSegments/view/1c77d587-1bd9-4803-8d13-89a01d2c23f3/image.png 

On the server, a helper class (CartHelper) with static methods encapsulates simple concepts (like “is this a discounted cart”?) and task routing. A class (CartDao) following the data access object (DAO) pattern for CRUD is implemented separately. Another class (PromoProcessor) implements the business logic for promotions. Both are called by CartHelper, and the DAO class is also called (for database updates that reflect promotional discounts) by the PromoProcessor 

On the client, one LWC (cart) is implemented as a container for another LWC (cartHeader) representing info like subtotal, tax, estimated shipping, and an array of LWC instances representing each product in the cart (cartItem), with product data, unit price, list price, discounted price, total. Each =child LWC receives data from the container in public variables specified in the child LWC’s .js file.  

Code implementing this design is available here 

Section 6

Conclusion: Filling the L3-L4 Gap

Some applications are obviously far too complex to build maintainably using low-code tools. But when a platform has a mature, high-powered low-code toolset like Salesforce, and when users are often looking for relatively simple process improvements, the line between “low code” and “full code” can blur. Many tasks are just slightly too complex for point-and-click solutions, but seem far too trivial to invest in writing thousands of lines of code, especially when the point-and-click solution is so nearly sufficient. We call this the L3-L4 gap, where L3 is low-code and L4 is full-code. Salesforce developers encounter this chasm all the time.  

Further, Salesforce (with SaaS deep in its DNA) iterates on the platform very rapidly, with major releases every quarter. Since your custom code is less coupled to the platform that Salesforce provides, new Salesforce releases can quickly start to make your full-code app look and feel out of date. Note that the Apex itself will probably not break: Salesforce is very careful about this. Obsolescence more commonly comes at the level of UX. For example, Salesforce released a new client-side experience in 2015 (Lightning). And yet many complex apps built with the old Salesforce web framework (VisualForce) have still not been updated (as of 2021). As a result, they look antiquated and confusing. But it would be very expensive to update these apps, since the old Visualforce is a server-side web framework while Lightning (Aura or LWC) is a client-side web framework. Everything besides the Apex would have to be rewritten.  

Besides UX obsolescence, custom point-to-point integrations built in Apex can grow stale unusually fast.  This results from the trivialness of Salesforce schema changes combined with the CRM’s position at the heart of the 360-degree view of the customer. Both sides of this sort of integration are unusually likely to change fast. Further, since Salesforce often interacts with legacy enterprise systems (because it is a fancy and expensive CRM) and cannot afford to talk only with systems at hip new tech or tech-first companies, the systems Salesforce integrates with do not necessarily follow modern RESTful or otherwise distributed- or cloud-first design practices. So on the whole, point-to-point integrations are even more brittle on Salesforce than on other platforms. 

Fortunately, a huge ecosystem has grown up around the Salesforce PaaS, centered on the Salesforce app store (AppExchange). Thousands of free and paid tools exist to solve business problems directly or simplify app development, e.g.:  

  • Open-source solutions, some provided by Salesforce (Salesforce Labs), many nowadays published on GitHub  
  • Focused task-specific automations (sometimes called Lightning Bolts)  
  • LWCs that are just as drop-in as standard Web Components, often published on GitHub  
  • Point-and-click integration middleware  
  • Rapid Application Development (RAD) tools that bridge the L3-L4 gap, abstract away platform details, implement modern UX, provide OOTB connectors to common data sources, etc. 

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}