A Decade of Code

Wax Nostalgic

Ten years ago, I walked into the Bluewolf office in NYC that overlooked Madison Square Park. I passed someone working in what looked like a phone booth, and I remember thinking, “wow that person must be super important, they have their own office!” After the tour and some brief intros, I was shuttled into that phone booth where my laptop awaited me. Turns out that person started the week before me and there just was no more desk space.

My first project was to help maintain a custom built CMS built using Ruby on Rails for some non-profit in the city. I don’t remember much of about it but I do remember breaking the staging environment, blocking the client from making any changes. Luckily they didn’t notice while my mentor and I panicked to figure out what we broke. Eventually that client contract ended and didn’t renew (for unrelated reasons, I assume), which meant I needed to start learning Salesforce development. 10 years and 6 jobs later, here I am. And if you’d indulge me for a bit, I’d like to share some lessons from along the way.

In No Particular Order, Lessons from a Decade of Code

  • Learning vim would probably make me very productive, or at least feel that way. I’ve given up every time I’ve tried
  • Everything is a form. It’s just inputs that you’re capturing for some other system to consume. This gives me existential dread sometimes
  • Rebuilding is almost never worth it, until it is
  • Being very responsive to every slack message will get you lots of praise. It will also quickly burn you out
  • I used to think being able to solve problems quickly during live meetings was a skill. It’s actually a a detriment that prevents other people from contributing that leads to poorly thought out solutions and should be discouraged
  • The value of meeting plummets after adding more than 3 people. I promise you it should just be an email
  • No new feature cycle, from ideation to delivery, should be less than a day. I don’t care how small that change is, it needs to be prioritized like everything else or else you’ll constantly be drowning in “quick changes”
  • Processes not only protect the product, but they also protect you. However they should also work for you, not the other way around
  • A sustainable pace is critical for any role that you want to last for more than 2 years
  • Burnout takes months to recover from. It’s not something you can just take a long weekend to fix, especially if you don’t address the cause when you get back
  • Don’t derive your value or your passion from work. It will only hurt you
  • Working from home is the best and I would NEVER work in an office again if I had the choice
  • Version control is the single most important piece of technology if you’re writing code. It’s the bare minimum and I refuse to work in any environment that doesn’t have it
  • Design patterns should serve as inspiration, not as rules to be followed dogmatically
  • The agile manifesto itself is really good - it’s a focus on building a process that works for those involved, favoring a frequent feedback loop. If you ever say “that’s not Agile”, you’re missing the point
  • Documentation doesn’t get enough love. Code can’t document itself - the code can only do what’s written, it’s bad at showing intent
  • You will NEVER beat Excel. You should embrace it and integrate with a spreadsheet instead
  • You’ll generally make more money switching jobs than a promotion
  • Always ask for more than what you’re making. If a recruiter asks what you’re looking, add 20% over what your currently making. If they ask how much you’re making, lie
  • Transactional data should be separate from analytics data. Specifically, putting analytics data on your sObjects like Account is an anti-pattern
  • Capture events that happened, not timestamps that imply something. FIGHT ME.
  • I hate pair programming and I think it should be ok if you never want to do it
  • Favor async over synchronous collaboration
  • Giving people the chance to hit that flow state is the cheapest way to make your employees happy
  • Invest in good tools (chairs, monitors, desk, etc). You’re using them for at least a third of your day
  • You don’t need a side project. If you have one, don’t hesitate to work on it during the work week. You’re getting your work done right? So no one should care and it counts as professional development
  • Take the time to experiment with technology. On company time.
  • Keep your personal projects off company assets. I don’t know how enforceable those technology tranfers are and I don’t want to find out
  • Recognize the signs of burnout - no one is going to do it for you, you have to advocate for yourself
  • Getting really good at one thing can be very lucrative as opposed to chasing the latest technologies
  • Max out company matching on your retirement account - it’s free money
  • You don’t have to be passionate about your job to be good at it
  • Your job is not your family. You can (and probably will) develop friendships on a personal level with your coworkers naturally, but the idea that your company can be family is a lie
  • You will have trash managers. Leave if you can
  • You will have incredible managers who actually do feel family. Thank them often and let them know
  • Just because you’re good at your job doesn’t mean you’d be good at managing others doing that job, or that you’d even like management
  • Think often what you like about a job. Lean into that. You don’t have to love your job, but maximizing the parts that bring you joy helps
  • If software development didn’t pay like it does, I absolutely would do something else, and that’s ok if you feel that way
  • Forgive those that came before you. You don’t know what time pressures, uncontrollable circumstances, etc. that led to the state of the codebase. Don’t get angry, just take this into account when you pad your estimates
  • Estimates can be pretty arbitrary, but they become less so the more you do them, and they are very important when it comes to planning
  • Don’t be the only person that can do your job, there should always be at least two so that you can go on vacation
  • Be kind to each other. Avoid toxic interactions and embrace those that bring you joy. This is really hard
  • Know which battles to fight. When you stand your ground it’ll be really apparent how much it matters
  • You are worth it. You are worth that promotion, that raise, that time off. You deserve to be in that job, you know what you are doing

I could probably do this all day, but this is enough for now. Here’s to 10 more years of code! Thank you everyone that helped get me here because I certaintly did not do it alone. And if you’re starting your journey, I hope I can help you as well.

Can we trick salesforce into doing reportable dynamic rollups?

Rollups. Why is it always rollups.

I hate rollups. I hate building them. I hate how they slow down things in Salesforce. I hate how they can accidentally block users from editing records. And I especially hate debugging them. “Why is this roll up field wrong?” I don’t know, there are so many points of failure when it comes to a roll up. Maybe the job hasn’t run yet and someone just added another record. Maybe an error was thrown and a record wasn’t accounted for. But sure, I’ll throw away a few hours to answer your question and by the time I have an answer the rollup corrected itself. I digress.

And I say all this as a contributor to the Declarative Rollup Summary open source project. I get why we need rollups. Not every org has access to an analytics tool and is limited to Salesforce reporting. And sometimes an orgs needs to answer the question “How many Account records have donated more than $1000?” and the person asking the question needs to do this with a report. So you build a rollup summary because the alternative is building a custom reporting tool and that’s it’s own set of problems. That’s why DLRS is such wonderful tool. I just hate it that it has to be like this. Or does it?

SOQL to the rescue

What infuriated me for years was that SOQL can actually do this for you with aggregate queries. For example, if you captured your donations for an Account in a child object called Donations, you could do the following with SOQL:

SELECT Account__c, SUM(Amount__c) FROM Donation__c GROUP BY Account__c HAVING SUM(Amount__c) > 1000

But the goal here is to make this reportable. Sure, you might get away with having devs and admins run these SOQL queries for the business, but part of scalability is providing a mechanism for self-service. So you make the rollup field.

Enter the Apex Connector Framework

I was playing with the Apex Connector Framework to pull some data into Salesforce from an API so that they could be reportable. The flow of this framework is as follows

  • Define fields on an external object (i.e. how will this object be represented in Salesforce?)
  • Extend the framework classes, which mainly allow you to interpret a SOQL query, sent the search criteria to an API with a callout
  • Parse the results and map to rows for the external object.

After you do this, the external object becomes reportable! So I thought, what if instead of doing a callout we just did the aggregate query from above and mapped those results to the external object. In other words, I have an external object called AccountRollup__x with a field TotalDonations__c, and every time it is queried, it does an aggregate of Account records?

Turns out, it kind of works and it’s reportable! You can run a report using filters on the TotalDonations field as if you were reporting on a regular rollup field.

See this repo for code samples of a prototype that does just this you can try in a scratch org: https://github.com/seanpat09/live-rollups

Caveats

I ran into a few issues while building this prototype.

  • For some reason, I could not display fields related Account fields on the report as I was getting a system error with a gack. That’s why I suggest linking to the summary field in the report with the Name column, which can give you access to the Account via the AccountRecord lookup field
  • When I tried to make the rollup summary record a parent to the Account, the report would just break, not allowing me to do the filtering at all
  • You’re limited to whatever SOQL limits you are normally limited to, so if you have a really high volume org, this might not work at all.
  • I’m not sure of the licensing on the Apex Connector Framework, but I was able to use it in an Enterprise org that does NOT have the license for Salesforce Connect.

So what does this mean? I’m not really sure yet to be honest. But if this works and scales scales, it might be another option to rollup summaries without having to run background jobs to maintain the data! Instead of debugging a job, you’re debugging SOQL queries instead, which might be much easier to maintain for your team.

Design Patterns in Salesforce: Abstract Factory

Design patterns are powerful tool to build reusuable code, but they are hard to implement in without examples and use cases. This is a series that follows the book Design Patterns: Elements of Resuable Object-Oriented Software a provides examples that relate to Salesforce

Abstract Factory

The Abstract Factory design pattern is useful for creating related objects that follow an interface. I find this pattern particularly useful for building resuable Lightning Web Components. Let’s use this pattern to build a resuable custom related list.

Use Case: Viewing grandchild records on a detail page.

Out of the box related lists in Salesforce are great for displaying child records on a detail page, but what if you wanted to display all the grand child records? Imagine a national charity that oversees local branches. Each branch is represented as an Account record, and the volunteers for that branch are represented as Contact records. When a volunteer is onboarded, there are several onboarding tasks required before they can start any work, such as a background check, filling out emergency contact info, and creating a user in their Salesforce Community.

Region managers oversee local branches in their assigned region, so in addition to other information on the Account, the Region Managers want to see all the outstanding tasks for all the Contacts on the Account detail page in a table (for the sake of this post let’s pretend this is the best solution).

A non-resuauble approach

If you didn’t care about resuability (which is ok if you don’t have that bandwidth!), you could build an LWC like this (I can’t guarantee this compiles)

//contactTasks.html -->

<template>
  <lightning-datatable
    key-field="id"
    data={data}
    columns={columns}>
  </lightning-datatable>
</template>

//contactTasks.js

import { LightningElement } from 'lwc';
import getTasks from '@salesforce/apex/ContactTasksController.getTasks';

const columns = [
  { label: 'Subject', fieldName: 'Subject' },
  { label: 'Assigned To', fieldName: 'WhoId'}
];

export default class ContactTasks extends LightningElement {
  data = [];
  columns = columns;
  recordId;

  async connectedCallback() {
      const data = await getTasks({ accountId: this.recordId });
      this.data = data;
  }
}

//ContactTasksController

public class ContactTasksController {
  @AuraEnabled
  public static List<Task> getTasks(Id accountId) {
    List<Task> allTasks = new List<Task>();
    for(Contact aContact : [SELECT (SELECT Subject, WhoId FROM Tasks) FROM Contact WHERE AccountId = :accountId]) {
      allTasks.addAll(aContact.Tasks);
    }

    return allTasks;
  }
}

A solution like this is fine, but in the future you might get a similar request where you want to display all tasks for all the Contacts related to an Opportunity. Or maybe just some other data. You could create a separate LWC with a separate Apex controller, but you might notice that the LWC isn’t doing all that much, just displaying data. Most of the differences is in building the columns and getting the data. Wouldn’t it be nice to abstract out the logic that builds the table data? Like some kind of abstract factory?

The Abstract Factory Pattern

The abstract factory pattern can be broken down into 3 parts:

  • A interface that specifies what an implementation must be able to build.
  • A consumer that can request a specific implementation, but can handle any implementation of the interface
  • Concrete implementations that fulfill the interface

So in this case we need an interface that can build the columns and rows for a table, a component that can specify which columns and tables to display and actually display them, and then 1 or more concrete implementations with the business logic. How would this look?

First let’s create some objects to define what we’re building:

public class CustomListColumn {
  public String label;
  public String fieldName;

  public CustomListColumn(String label, Object fieldName) {
    this.label = label;
    this.fieldName = fieldName;
  }
}

public class CustomListRow {
  public Object value;

  public CustomListRow(Object value) {
    this.value = value;
  }
}

Next our interface for our factory.

public interface ICustomListFactory {
  List<CustomListColumn> buildColumns();
  List<CustomListRow> buildRows(Id parentId);
}

And then our concrete implementation of that factory

public class AccountContactsTaskListFactory implements ICustomListFactory{
  public List<CustomListColumn> buildColumns() {
    List<CustomListColumn> columns = new List<CustomListColumn>();
    columns.add(
      new CustomListColumn('Subject', 'Subject'),
      new CustomListColumn('Assigned To', 'WhoId')
    );
  }
  public List<CustomListRow> buildRows(Id parentId) {
    List<CustomListRow> allTasks = new List<CustomListRow>();
    for(Contact aContact : [SELECT (SELECT Subject, WhoId FROM Tasks) FROM Contact WHERE AccountId = :parentId]) {
      for(Task aTask: aContact.Tasks) {
        allTasks.add(new CustomListRow(aTask));
      }
    }
    return allTasks;
  }
}

Next let’s make our component more generalized. It now gets the columns in addition to the rows from the Apex controller. It also passes a listType parameter to specify which implementation should be used

//customRelatedList.html -->

<template>
  <lightning-datatable
    key-field="id"
    data={data}
    columns={columns}>
  </lightning-datatable>
</template>

//contactTasks.js

import { LightningElement } from 'lwc';
import getTableData from '@salesforce/apex/CustomRelatedListController.getTableData';

export default class CustomRelatedList extends LightningElement {
  data = [];
  columns = columns;
  recordId;

  @api
  listType

  async connectedCallback() {
      const tableData = await getTableData({ recordId: this.recordId, listType: this.listType });
      this.data = tableData.rows;
      this.columns = tableData.columns;
  }
}

Finally we create a controller that will handle getting the correct implementation based on the listType passed from the LWC

public class CustomRelatedListController {
  @AuraEnabled
  public TableData getTableData(Id recordId, String listType) {
    ICustomListFactory factory = getCustomListFactory(listType);
    
    TableData data = new TableData();
    data.rows = factory.getRows(recordId);
    data.columns = factory.getColumns();

    return data;
  }

  private ICustomListFactory getCustomListFactory(String listType) {
    if(listType === 'AccountContactsTasks') {
      return new AccountContactsTaskListFactory();
    } else {
      throw CustomRelatedListControllerException('List Type: ' + listType + ' not supported');
    }
  }


  public TableData {
    @AuraEnabled
    List<CustomListColumn> columns;
    List<CustomListRow> rows;
  }

  public CustomRelatedListControllerException extends Exception {}
}

Benefits

This seems like a lot of extra code and for just a single use case it probably is. The value comes with its reuse. Let’s add another table, this time displaying Contact information related to an Opportunity. Specifically, the Contact’s full name, parent account, grandparent Account (i.e. up an additional Account level in the hierarchy), and the Account website.

Here’s the concrete implementation:

public class OpportunityContactListFactory implements ICustomListFactory{
  public List<CustomListColumn> buildColumns() {
    List<CustomListColumn> columns = new List<CustomListColumn>();
    columns.add(
      new CustomListColumn('Full Name', 'fullName'),
      new CustomListColumn('Parent Account', 'parentAccount'),
      new CustomListColumn('Grandparent Account', 'grandParentAccount'),
      new CustomListColumn('Website', 'website')
    );
  }
  public List<CustomListRow> buildRows(Id parentId) {
    List<CustomListRow> contacts = new List<CustomListRow>();
    for(OpportunityContactRole ocr :
      [ SELECT Contact.Fullname,
               Contact.Account.Name,
               Contact.Account.Account.Name,
               Contact.Account.Website
        FROM OpportunityContactRole WHERE OpportunitId = :parentId]
    ) {
        Map<String, String> contactRow = new Map<String, String>{
          'fullName' => ocr.Contact.FullName,
          'parentAccount' => ocr.Contact.Account.Name,
          'grandParentAccount' => ocr.Contact.Account.Account.Name,
          'website' => ocr.Contact.Account.Website,
        }
        contacts.add(contactRow);
    }
    return contacts;
  }
}

And then we update the controller to handle this new list type:

  private ICustomListFactory getCustomListFactory(String listType) {
    if(listType === 'AccountContactsTasks') {
      return new AccountContactsTaskListFactory();
    } else if(listType === 'OpportunityContacts') {
      return new OpportunityContactListFactory();
    } else {
      throw CustomRelatedListControllerException('List Type: ' + listType + ' not supported');
    }
  }

And now our LWC can handle a different use case given a different listType API value, without having to change any of its code! Arguably the getCustomListFactory method should be pulled into its own class so that the controller also does not have to be updated either.

I find myself using this pattern often with LWCs to take advantage of the reusability of components. It can take a little extra upfront work, but can save you a lot of time in the long run in terms of code you don’t have to write and also for creating small testable components.