Pages

Showing posts with label triggers. Show all posts
Showing posts with label triggers. Show all posts

Thursday, October 18, 2018

Salesforce Custom Metadata vs. Custom Objects: Where Should I Put My Configuration and Validation Settings?

(Version française en dessous)

Yesterday, a colleague asked me the difference between "custom metadata" and "custom objects" for storing "configuration and validation" information in Salesforce.

My answer is:

  • Custom metadata if you can. Salesforce says so.
    And it survives sandbox refreshes!
  • Data tables (custom objects) if you truly need it to be part of your data
    (e.g. people will want to include it, hyperlinked to other data, in reports).

When I wrote an Apex trigger that determined which User should "own" each "Admissions Application" in our org, I ended up splitting the configuration data in two.

Here's why:

Custom Metadata

Used for a table that helps Apex code ask, "Who does this?"

  • key: the code for a graduate degree we offer
  • value: a username responsible for recruiting people to that graduate degree

Data (Custom Objects)

Used for a list of U.S. states, their full spellings, and fields with "lookup" links to the User table indicating who does what work for that state.

  • There was strong demand to generate native Salesforce reports per User showing all the states they're responsible for and the high schools in those states. It made sense to ensure that the "high school" table could have a "lookup" field to this "states" table.
  • Custom metadata can have "master-detail" and "lookup" relational links to other custom metadata, but it can't link to ordinary data.
    • This meant we needed to store the the "states" as data (custom objects), even though we would also be using it as configuration information for Apex triggers.

UI & Editability Considerations

I'll let you in on a dirty little secret about another reason I used data tables ("custom objects") for most of the Undergraduate Admissions counselor assignment configuration data.

Undergraduate Admissions tweaks their "recruiter assignment" business rules several times a year. The real-world configuration data for their business is a lot more complex than a simple "list of U.S. states."

I'll be honest: Salesforce's user interfaces for hand-editing, viewing, and bulk-editing data are a lot more end-user-friendly than their user interfaces for the same operations on custom metadata, and setting granular "edit" permissions is a lot more sysadmin-friendly for data. I wanted to make sure end users, not sysadmins, were the ones whose time was spent tweaking the configuration several times a year!

I was thoroughly scolded at Dreamforce. Actually, I stand by my decision to use "data" tables, because there truly is a business need to report on the configuration data alongside normal data. But ... building your own user interfaces (typically using Lightning Components) to help end users edit custom metadata was a big theme. You have been warned:

  1. Dan Appleman's "Build Awesome Configuration Pages with Lightning Components & Custom Metadata"
  2. Gustavo Melendez & Krystian Charubin's "Crafting Flexible APIs in Apex Using Custom Metadata"
  3. Beth Breisness & Randi Wilson's "Create Guided User Experiences for Managing ISV Custom Metadata"


🇫🇷 - en français

Une collègue, peu familière avec Salesforce, m'a demandĂ©e quelle Ă©tait la diffĂ©rence entre « mĂ©tadonnĂ©es personnalisĂ©es » et « objets personnalisĂ©s » pour stocker des donnĂ©es de configuration et de validation dans une organisation Salesforce.

J'ai répondu:

  • Selon Salesforce, s'il est possible, utilisez des mĂ©tadonnĂ©es personnalisĂ©es.
    (Elles survivent l'actualisation d'un environnement sandbox.)

  • Stockez vos informations aux tables de bases de donnĂ©es (objets personnalisĂ©s) s'il faut crĂ©er des relations aux autres objets de votre organisation.
    (Par exemple, s'il y a des utilisateurs qui vont générer des rapports sur ces données.)

Mon approche

Lorsque j'ai Ă©crit un dĂ©clencheur Apex pour attribuer les enregistrements de notre objet « demande d'admission » aux utilisateurs corrects, j'ai fini par utiliser les deux approches:

Métadonnées personnalisées

J'ai choisi une type de mĂ©tadonnĂ©es personnalisĂ©es pour stocker des donnĂ©es simples concernant « qui gère chaque spĂ©cialisation ? »

  • clĂ©: le code qui indique un diplĂ´me d'Ă©tudes supĂ©rieures que l'universitĂ© offre
  • valeur: un nom d'utilisateur chargĂ© de recruter des Ă©tudiants au diplĂ´me

Objets personnalisés

J'ai choisi un objet personalisé pour stocker une liste d'états américains (abréviation et nom), avec quelques champs de référence indiquant les utilisateurs qui y gèrent divers aspects du recrutement d'étudiants en license.

  • Nos utilisateurs ont besoin de gĂ©nĂ©rer des rapports, regroupĂ©s par utilisateur, avec les Ă©tats qu'ils gèrent et les lycĂ©es qui y sont situĂ©s. Donc il fallait pouvoir crĂ©er une relation de l'objet « lycĂ©e » Ă  l'objet « Ă©tat ».
  • On peut crĂ©er des relations entre mĂ©tadonnĂ©es personnalisĂ©es, mais pas entre les types de mĂ©tadonnĂ©es personnalisĂ©es et les objets personalisĂ©s.
    • Donc j'ai choisi un modèle « objet personnalisĂ© » pour stocker les informations Ă  propos des Ă©tats, mĂŞme si ces informations servent aussi comme donnĂ©es de configuration pour un dĂ©clencheur Apex.

Considérations au sujet de l'interface utilisateur et au contrôle d'accès

Je vais vous rĂ©vĂ©ler un secret : ce n'est pas qu‘Ă  propos des besoins relationnels que j'ai choisi le modèle « objet personnalisĂ© » pour les informations Ă  propos de la gestion du recrutement en licence.

Le service admission en license changent les règles de « qui gère quoi » plusieurs fois par an. Et les donnĂ©es de configuration sont, en rĂ©alitĂ©, beaucoup plus complexes qu'une seule liste d'Ă©tats.

Actuellement, les interfaces utilisateur de Salesforce pour voir et enregistrer des donnĂ©es aux objets personnalisĂ©s sont supĂ©rieures Ă  celles pour les mĂ©tadonnĂ©es personnalisĂ©es – surtout si l'on est utilisateur ordinaire. Le contrĂ´le d'accès, aussi, est plus facile Ă  gĂ©rer pour les administrateurs. Je voulais m'assurer que ça soit des utilisateurs ordinaires qui font plusieurs fois par an cette saisie de donnĂ©es, et non pas les administrateurs !

On m'a fait honte de cette position Ă  Dreamforce. Ben, je maintiens ma choix d'objet personnalisĂ© pour raisons de relations entre objets. Mais … on a beaucoup rĂ©pĂ©tĂ© qu'il est de bonne pratique de construire ses propres interfaces utilisateur pour autoriser des utilisateurs ordinaires Ă  modifier les mĂ©tadonnĂ©es personnalisĂ©es en toute sĂ©curitĂ©. Je vous aurai prĂ©venus !

Vidéos pertinentes de Dreamforce

  1. « Build Awesome Configuration Pages with Lightning Components & Custom Metadata » de Dan Appleman
  2. « Crafting Flexible APIs in Apex Using Custom Metadata » de Gustavo Melendez & Krystian Charubin
  3. « Create Guided User Experiences for Managing ISV Custom Metadata » de Beth Breisness & Randi Wilson

Wednesday, June 13, 2018

Trigger/Process/Workflow or Scheduled Script/Tool?

I often get asked to write triggers to detect the existence of certain types of data in Salesforce and, when such data exists, make a modification to some other data.

The only problem is, writing "a trigger" isn't always easy to do in a well-normalized database (that is, a database that leverages master-detail and lookup relationships to avoid redundant data entry).

Take, for example, an architecture where "App Document" records have a master-detail relationship to a parent "Admissions Application" and the "Admissions Application," in turn, has a master-detail relationship to a parent "Contact" record.

The other day, I was asked to automatically flip the "Status" to "Waived" on any "App Document" records that meet the following criteria:

  • The record is of type "English Proficiency" and is in blank/"Required" status
  • The record's parent "Application" has an "application citizenship category" of "International" and a "level" of "Undergraduate"
  • The record's grandparent "Contact" has a "foreign citizenship country" of "United Kingdom," "Ireland," "Canada," "Australia," "New Zealand," (etc.)

This is ill-suited to a Trigger/Process/Workflow because I would actually need THREE "insert"/"update" automations, each with relatively redundant code:

  1. one for all Contacts
    (in case the citizenship country changes ... then go looking for appropriate "child" apps and "grandchild" documents)
  2. one for all Applications
    (in case the "application citizenship category" or "level" changes ... then double-check the parent Contact's citizenship and go looking for "child" documents)
  3. and one for all App Documents
    (check the parent & grandparent details and change self if appropriate)

Yikes! That's a lot of redundant trigger code, and some of those operations aren't very efficient against Salesforce governor limits.

Especially since just one SOQL query can easily fetch the ID of all "App Document" records whose "Status" needs to be set to "Waived":

SELECT Id
FROM AdmDocument__c
WHERE Type__c='English Proficiency'
AND (Status__c = NULL OR Status__c = 'Required')
AND Application__r.Level__r.Name='Undergraduate'
AND Application__r.Citizenship_Category__c='International'
AND Application.Contact__r.Foreign_Citizenship_Country__c IN 
   ('Australia','Canada','Ireland','New Zealand','United Kingdom')

When someone asks you to "write a trigger" or "write a process builder" or "write a workflow" to automate data migration inside of Salesforce, be sure to ask yourself, "How many tables' values changing could cause a scenario to arise that would make this data need to be modified as requested?"

If the answer is "2 or more," and especially if it's "3 or more," think hard about whether "every 15 minutes" / "daily" is frequent enough for your end users to see "automatic fixes," and whether a SOQL query could extract the IDs of the records that need to be changed.

If you can come up with a SOQL query that represents the "problem records," you should be able to use a schedulable ETL tool or scheduled Apex to "extract and load back" with much lower overhead against governor limits than triggers/processes/workflows would incur. Your future self will also thank you when someone (inevitably) asks for a change to your script.

Tuesday, January 16, 2018

Re-Parented Child Objects Don't Fire Triggers In Parent Record Merges

Today I learned, via the Salesforce forums:

  • In Salesforce, if you merge 2 "Contact" records, and if you have a bunch of "child"-table records pointing to the "about-to-be-deleted" Contact record as a foreign key, Salesforce will automatically change the foreign key cross-reference in the "child" records to the ID of the "surviving" Contact record, and it will update those child records' "last modified" timestamp, BUT it will not allow any "UPDATE" triggers from those child tables to fire.
  • The only thing you can latch onto to detect that a "Contact merge" has just happened is the "AFTER DELETE" trigger-context against the "Contact" table (you can detect a "deleted but merged" Contact in that context from an ordinary "deleted" Contact because it has a "MasterRecordId" value).
    You can't "detect" that a "child" record has just been "re-parented" in the context of a "merge."
  • Annoyingly, once you're that far into the merge ("after contact delete" trigger context), you can no longer tell which "child" records were pointing to the old "parent" because that's long since been "fixed" by Salesforce.
    All you can see is which "child" records are now cross-referencing the surviving "Contact" – which includes all "child" records that were already pointing to the surviving "Contact" in the first place.
  • Finally, if you use that knowledge to kick off DML against surviving Contacts' "child" records, you must be careful, because "after Contact delete" is still in the context of the initial Contact merge, and you have to avoid loops (Salesforce will yell at you if you don't).
    If your DML against the "child" records kicks off triggers that result in more DML against one of the very Contacts involved in the merge, you need to make sure you kick off the DML against the "child" records in a "@future" context to force a brand new "execution context" (roughly like a transaction, but a little different in some ways – see Dan Appleman's Advanced Apex book).

Wednesday, October 18, 2017

Recursion-Reduction Tips For Apex Trigger Code

I wanted to share with you some lessons I learned while working on a trigger.


Because a trigger against a "just-saved" record can kick off another "save" operation on that record (typically the point of a trigger), the very same trigger can get re-fired during what's known as the same "execution context."

In Apex trigger programming, it's considered best-practice to make sure that any subsequent re-firings of the same trigger against the same record don't waste valuable CPU cycles when this happens (because Salesforce limits them within an "execution context").

Therefore, when writing a trigger that "does ____ for a just-saved record," it's important to make sure that, at some point, the trigger saves the ID of that record into a "I already did _____ on all of these records" flag that's viewable across all of these recursions (usually a class-level "Static"-flagged "Set<Id>"-typed variable in the trigger handler).

And, of course, you need to program your trigger to pay attention to its own flags and avoid running expensive "consider doing ____" code against records already in that "already-did-____" set of IDs.


The interesting question is: When do you set the "I already did _____" flag?
 

  • In certain cases, one can trust that all field-values contributing to a trigger's yes/no decision of "should I do _____ to this just-saved record?" will be set the moment that the record is first saved.
     
    In those cases, the most efficient place in your trigger code to set the "I already did _____ on this record" flag is "as soon as the trigger has seen the record for the first time, no matter whether it ends up qualifying for 'doing ____' or not."
     
    That's how I usually write my triggers if I can, since it's the most efficient way to write the trigger.
     
     
  • However, in certain cases (often discovered when people test your newly-written trigger and tell you that it fails under normal usage circumstances through 3rd-party record-editing environments like external portals), the values contributing to the answer to "should I do ____ to this record?" change so that the answer goes from "no" to "yes" in the middle of the "execution context."
     
    For example, other "triggers" or equivalent pieces of code do some post-processing to the just-saved record, and it's only after those pieces of code re-save the record that the answer flips to "yes."
     
    In those cases, the most efficient place in your trigger code to set the "I already did ______" on this record" flag is "as soon as the trigger has determined that it needs to do ______ to the record."
     
    This, unfortunately, will make the trigger run "Should I do _____?" checks all the way through the execution context for records that remain "no" throughout. That's why it's less efficient.
     
    But sometimes, it's simply necessary in cases where the answer can flip from "no" to "yes" mid-execution-context.
     
     

If one is comfortable authoring / editing the triggers/processes/workflows that are responsible for such impactful mid-execution-context value-changes, sometimes it's possible to refactor them so that they're simply part of the same "trigger handler" code as the one you're in the middle of writing.
You could precisely control the order of code execution "after a record is saved" and author these actions in a way that ensure there will never be any "mid-execution-context surprise value-changes."
That might let you use the more efficient recursion-reduction pattern instead.

Sometimes, though, there's nothing you can do but choose the 2nd option.

Wednesday, October 12, 2016

Dreamforce 2016 Lessons Learned

Lucky me, I got to attend Dreamforce. I had colleagues going to plain-English "what you can do with Salesforce" talks, so I filled my days with "how to program Salesforce" lessons. Here's a summary of what I learned.

(Also, I plan to put out a "how to install WinPython on Windows" walk-through but haven't had time to capture screenshots of the version I found that works well without administrator rights. It'll happen, though!)

  1. I got to go to a "gripe to the people who maintain Salesforce" session and gripe about certain "relational databases have been doing this for decades--why doesn't Salesforce?" things (e.g. the fact that data-cleansing "trigger" code can't be written against certain data tables in Salesforce). I learned some interesting history about why such things are broken. (For the triggers, the fact that the tables having data in them had about a 3-year head start on triggers existing at all. Which means you have to rebuild the tables or something, and apparently that's expensive in Salesforce-employee-time for not-many-customers, lots-of-rows tables, so money to pay someone to do it has to be budgeted from the top at Salesforce.) Ultimately, as I suspected, the almighty dollar is an issue. Fixing such basic functionality doesn't easily compete with adding other features that are better at bringing in new multi-million-dollar contracts to Salesforce, so the problem gets picked at slowly as limited budgets allow. But it was fun to get to tell someone whose job it is to fix these issues how annoying these issues are to his face and get sympathetically nodded to!
  2. Salesforce is pushing a new "artificial intelligence" product they just acquired pretty hard, but it seems to me that it's for companies whose business models mean they can be "fuzzy" about how they handle customer data. (One of the brag-stories was something along the lines of, "We didn't like our 'unsubscribe rate,' so we used AI to predict who was likely to unsubscribe and to preemptively unsubscribe them!") That said, I didn't actually go to any sessions about Einstein - it just had a display in the way of my coding classes. A marketing colleague was way more interested (e.g. mining Facebook/LinkedIn data).
  3. There's a new, alternative style of coding available for building custom reporting/data-entry screens inside Salesforce ("Lightning Components" instead of "Visualforce"). Our web developers don't write either right now, but if they did, they'd be happy to know that the new option is more modern-web-development-ey than the old option.
  4. There's an option to turn on a new look & feel for the user interface in Salesforce ("Lightning Experience"). It's been pretty hideous (a bunch of icons instead of words), but it's getting better (they brought the words back). Not sure if it's worth the work. The new & old user-interfaces have different features + some overlap, so it's not just flipping a switch. I learned some ways to find out what's around & what isn't + to preview what our org would look like in Lightning Experience. Overall, I think our users tend to want to be able to see lots of data at a time, and font sizes and whitespace seem smaller in the "Classic" UI, so I think "Classic" is better for us.
  5. I learned some things I can do to make sure that my administrator rights for a web application I have hosted externally are secure (e.g. lock down my admin account with two-factor authentication).
  6. I thought I was writing one type of test ("unit tests") against code I was writing in Salesforce, but it turns out it's actually closer to another ("integration tests"). Probably OK because "integration tests" tend to be closer to answering the question, "does this do what the end-user asked for?" Not sure if writing true "unit tests" is actually necessary for what I'm writing, but good to know the difference and some new things to learn ("mocking") if I ever want to do so.
  7. I learned some code necessary to ensure that code I write in Salesforce respects normal user-access settings (it goes beyond "with sharing"). That said, most of what I've written purposely "plays God" & takes care of stuff a user can't. But perhaps some of it doesn't have to "play God," so this is a nice tool to have in my toolbelt. I learned it last year but went for a refresher this year and plan to start using it more.
  8. Database triggers (code that executes when people save data in Salesforce) can make "asynchronous" calls to external services that take a while to return results (e.g. "Hey Google, what's the driving time between these two addresses?"). However, you want to make sure you write such code only for database-data-change conditions that are likely not to happen too often (e.g. less than 20 times per 10 minutes, or less than a few hundred times per day), because only 50 such calls can stack up in Salesforce at a time, and the 51st+ just kind of fail, possibly irrecoverably).
  9. I learned that, with Apex, I can write my own REST API that interacts with my Salesforce org, rather than relying on the Salesforce REST API. This can potentially let me reduce the number of "API calls" I have to make when, say, interacting with my Salesforce org from Python running on my hard drive. Potentially. Doesn't yet pass the XKCD automation test for my work.

Thursday, July 28, 2016

Wednesday, April 6, 2016

Define and Initialize a Map, List, and Set in Apex

I use Dave Helgerson's blog post "Define and Initialize a Map, List, and Set in Apex" pretty much every time I code. Keep it in your bookmarks and you can essentially type "map" or "list" or "set" in your browser URL and have a quick-reference whenever you need it.

Monday, February 29, 2016

General Tips For Oracle PL/SQL Developers Switching To Salesforce

Here are some notes from the trenches on what it's like to try to do switch from Oracle PL/SQL to Apex for your anonymous database modification code & stored procedure & trigger development:

  1. Trigger-writing in Salesforce essentially means "surrounding a crippled version of SQL in a crippled version of Java."
    It's definitely doable, but a pretty big paradigm shift from Oracle PL/SQL trigger programming and a bit of a shock to the system at first.
     
  2. You can't build as complicated of "SOQL" queries in Salesforce as you can build "SQL" queries in Oracle.
    • The "Salesforcey" way around this is typically to cache the results of simpler "SOQL" queries in Java-like "List" & "Map" & "Set" data structures and then use Java-like code to loop through the cached data and finish off the aggregations + filters + joins.
       
  3. Some "SOQL" queries can be made more elegant by putting half of their complexity into the definition of a "formula field" on a given object - but the downside is that you now have your code split between the trigger code and the definitions of the tables themselves. Nevertheless, for example, rather than hard-code this logic into the trigger, you could add a "is SAT verbal score greater than 600 and SAT math score greater than 500?" Boolean formula field to the "Test Score" table in Salesforce and just have the trigger check it for "==TRUE."
     
  4. Although you'll be writing in Java-like code, except when you're creating "reference" objects (like here), you won't be doing a lot of "object-oriented" programming.
    Mostly, you'll be defining functions and writing step-by-step procedural code that calls them.
    In code, this will play out as most of your "classes" and "methods" having the modifier "static."
    So don't worry too much about not being experienced at object-oriented programming.
     
  5. Just as you can write "stored procedures" but not attach them to a database trigger in PL/SQL, in Salesforce you can save snippets of code that you're not sure how often you'll actually need to use (but may suddenly need to invoke on demand) in Java-like "classes."
     
  6. Just as you can execute "anonymous procedures" in PL/SQL, in Salesforce you can anonymously execute lines of Java-around-SQL-like code. This is often useful for one-off data fixes that are too complicated for "export, fix, & re-import" fixes with Data Loader or "MassImpact" fixes with DemandTools.
     
  7. "Export, fix, & re-import" fixes are often made much faster by doing the "fix" step with Python rather than Excel.
    • Anyone responsible for such work will find it handy to have a Python environment + the "NumPy" & "Pandas" & "CSV" plugins installed on their computer and an IDE for Python code execution (the "Spyder IDE" / "WinPython" environment works well for me).
       
  8. Get a copy of Dan Appleman's "Advanced Apex Programming" to get more "how to make your code efficient" tips and to learn subtle "variable scope" differences in Salesforce's Java variation. Also handy is a StackOverflow account, because we have our own sub-board at salesforce.stackexchange.com.

Thursday, February 4, 2016

The Singleton Pattern And "Commonly Used Data"

If you hadn't noticed, Salesforce stored-procedure & trigger programming is basically using Java wrapped around crippled SQL. Which means it's a good idea to be up on your object-oriented design patterns.

One of those that I just used to avoid copying the same SOQL query into a 5th trigger-handler's source code is the Singleton pattern.
(I'll be going back and fixing the 4 older trigger-handlers. Yes, I let it get that bad before I had time to fix it.)


Quick refresher for newer programmers: in object-oriented programming, a "class" is a "cookie cutter" and an "object" is each individual cookie you cut. So, a "car cookie cutter" would say, "Make sure that every car has a number of wheels, a number of doors, a make, a model, a paint color, and a unique serial number. It also needs to be driveable." A "car object" would be the "the 4-wheel 4-door red Ford Taurus with serial #XYZABC12345" - these are the values stored in its "class-level" variables. Like all cars, it would be driveable (this is a "method" that's part of the definition of all "cars" and it comes with this particular car just because it's a car).

When you're defining Apex triggers, you typically don't actually do object-oriented programming with classes. Typically, you define & use "static" methods for the "car cookie cutter" and directly invoke the "drive()" method. Which is a little hard to imagine as a metaphor - driving the car factory instead of the car - so sorry about that. But just trust that, well, if it's not a method that actually needs a particular car to exist, such methods can be defined and used and that's probably what you're actually used to doing as an Apex trigger programmer.


But ... sometimes it IS useful to manufacture a particular car!

And, more specifically, if what you'd like to do is put a single car in a museum and call it "best car ever made" and have all of your code just copy its color and number of doors, you want a Singleton. The reason to do this "copy whatever's in the museum" pattern is that this keeps you from having to hard-code values like "silver" or "4-door." If you ever change your mind about these details, you just change the car in the museum.

Think of a "Singleton" class as the museum itself. It's not open to the public - all you can do is ask the guard, "What color is the car that's in there right now?" You don't even get to know whether they really have a car in there or not. You just know that the sign on the front door says you can ask the guard what color the car is, how many doors it has, etc.

(There's an extension on the "Singleton" programming pattern where, for efficiency, the museum doesn't even exist until the first visitor asks a tourist-info-kiosk for directions to it - at which point the city quickly scrambles to build a museum complete with "4-wheel 2-door silver Ford Taurus" inside before she gets there. Designing the museum this way is called "lazy instantiation." In my code below, I took this pattern a bit further for rare questions & named it "lazy data fetch" in my comments. Think of it like not bothering to paint the car until the first time someone asks the guard what color it is.)


So here's my code for accessing "commonly used info" like "the default Account ID for new Contacts," "the default Owner for records," "the Record Type ID of a given object & record-type-name," etc.

To call the "Singleton" code and ask it "the Record Type ID of a given object & record-type-name," I just write:

Id rtID = UtilityDefaultInfoOftenNeeded.getInstance().getRecordTypeId('Admissions', 'Opportunity');

The call to "getInstance()" asks for directions to the "useful settings" museum (at which point the city scrambles to build one, complete with answers to questions I can ask the guard, if it's not built yet) and gives my code directions to that museum.

For legibility if I'm asking the guard at the door lots of questions, I might instead write:

UtilityDefaultInfoOftenNeeded useful = UtilityDefaultInfoOftenNeeded.getInstance();
Id rtID = useful.getRecordTypeId('Admissions', 'Opportunity');
// (etc.)

Note that I don't say "new UtilityDefaultInfoOftenNeeded()." I made the "constructor" private on purpose to prevent that. Instead, I say "UtilityDefaultInfoOftenNeeded.getInstance()."

In fact, the inability to use "new ..." is really what makes it a "Singleton." You're not allowed to demand that a new "best car ever" or "default settings" museum be built. You can only ask for directions to it via a "public static" method like "getInstance()" and trust that one single museum will exist by the time "getInstance()" returns directions to the museum.


Here's how "UtilityDefaultInfoOftenNeeded" is written (as a "lazy-instantiation singleton"):

public class UtilityDefaultInfoOftenNeeded {
    
    // Please note that many values returned by the "getter" methods of this class could return null,
    // so be sure to check returned values for "== null" if that is important to your code calling these methods.
    
    private static UtilityDefaultInfoOftenNeeded instance = null;
    
    private Id defaultAccountId; // Fallback "Account" for new "Contact" records where not specified
    private Id defaultOwnerID; // Fallback record owner ID for records in the database
    private Id defaultLeadNurturerID; // The User ID of the "default" "Lead Nurturer" staff member

    private Map rtIDs = new Map(); // For holding data from the "Record Type" object


   
    // Private constructor - this is a Singleton class
    private UtilityDefaultInfoOftenNeeded() {
        // In this constructor, we do any computationally expensive or limit-worrisome computations
        // that should be done as soon as the object is instantiated (rather than when the data
        // is requested through a public object-level "getter" method).
        // Checking for "null" is not necessary because this is a constructor - all variables are null so far.
        // We will not populate every object-level variable in this constructor.
        // Some object-level variables' values are rarely needed and more expensive to compute, so we will "lazy data fetch"
        // them in their getter methods.
            if (Schema.getGlobalDescribe().keySet().contains('default_settings__c')) {
                // Set default Contact Account ID, Owner ID, and Lead Nurturer User ID
                // First, grab the "Default Settings" custom setting:
                Default_Settings__c cs = Default_Settings__c.getInstance();
                System.debug('Default Settings custom setting consists of:  ' + cs);
                // Next, initialize Default IDs from this setting:
                defaultAccountId = String.isBlank(cs.Account_ID__c) ? null : cs.Account_ID__c;
                defaultOwnerID = String.isBlank(cs.Owner_ID__c) ? null : cs.Owner_ID__c;
                defaultLeadNurturerID = String.isBlank(cs.Lead_Nurturer_User_ID__c) ? null : cs.Lead_Nurturer_User_ID__c;
            }
    }
    
    // There should be just 1 public method in this class that is STATIC:  "getInstance()."
    public static UtilityDefaultInfoOftenNeeded getInstance() {
        // Lazy instantiation
        if (instance == null) instance = new UtilityDefaultInfoOftenNeeded();
        return instance;
    }
    
    // These three methods may return null Id-typed values, so be sure to check the return value before using.
    // (Developer note - no need to "lazy-data-fetch" these values, as they "lazy-fetched" upon instantiation in the constructor.)
    public Id getDefaultAccountId() {return defaultAccountId;}
    public Id getDefaultOwnerID() {return defaultOwnerID;}
    public Id getDefaultLeadNurturerID() {return defaultLeadNurturerID;}
    
    // This method may return a null Id-typed value, so be sure to check the return value before using.
    public Id getRecordTypeId(String devName, String sObjName) {
        if (rtIDs.isEmpty()) {
            // Lazy data fetch of entire "RecordType" table into this object's "rtIDs" private variable
            for (RecordType rt : [SELECT Id, DeveloperName, SObjectType FROM RecordType]) {
                rtIDs.put((rt.DeveloperName + ';' + rt.SObjectType), rt.Id);
            }
        }
        // Grab the relevant ID and return it (or "null" if not found)
        Id idToReturn = null;
        if (rtIDs.containsKey(devName + ';' + sObjName)) {idToReturn = rtIDs.get(devName + ';' + sObjName);}
        return idToReturn;
    }
    
}

(If you see "string string" end tags at the end of this code, ignore them - my code formatter is inserting them.)


And here's its test class:

@isTest
private class UtilityDefaultInfoOftenNeededTest {

    static testMethod void testInfoOftenNeeded () {
        
        // Set up default custom settings (these are data in a table, so they don't exist in seeAllData=false test classes & need to be made in the test)
        DefaultTestDataAccountFactory.makeAndSetADefaultTestingAccount();
        DefaultTestDataOwnerFactory.makeAndSetADefaultTestingOwner();
        DefaultTestDataLeadNurturerFactory.makeAndSetADefaultTestingLeadNurturer();

        UtilityDefaultInfoOftenNeeded useful = UtilityDefaultInfoOftenNeeded.getInstance();
        
        Test.startTest();
        Test.stopTest();
        
        System.assertEquals(TRUE, useful.getDefaultAccountId() != null, 'getDefaultAccountId() is null.');
        System.assertEquals(TRUE, useful.getDefaultOwnerID() != null, 'getDefaultOwnerID() is null.');
        System.assertEquals(TRUE, useful.getDefaultLeadNurturerID() != null, 'getDefaultLeadNurturerID() is null.');
        System.assertEquals([SELECT Id, DeveloperName, SObjectType FROM RecordType WHERE DeveloperName = 'Admissions' AND SObjectType = 'Opportunity'].Id, useful.getRecordTypeId('Admissions', 'Opportunity'), 'getRecordTypeId(...) is null.');
    }
    
}

Tuesday, January 5, 2016

Mid-Dormancy Updates

The good news is that I'm more used to Apex+SOQL than PL/SQL+SQL now. I can't say the same of SQL vs. SOQL (SOQL still drives me bonkers), but I'm not floundering enough to have to write down my daily frustrations any more. In fact, I'd say I'm starting to bang out Apex triggers rather often!

I've also had the chance to work on some other really cool projects. When I went to Dreamforce, I vacationed at the home of a hobby-acquaintance who happens to be part of San Francisco's tech boom crowd. Between hanging out in the Dreamforce dev zone and spending time with him and his friends, I got inspired to get my hands dirtier with not-purely-database Salesforce programming.

  • With a friend's help, I developed a Python (Pandas & OAuth & Flask) web application and hosted it on Heroku.com. It facilitates a colleague's daily updates/inserts of external data into Salesforce using Name+Email as a matching key. (Name+Email for matching is not natively supported by Salesforce data loaders.)

    My colleague provides my tool a .CSV file of names, email addresses, and data to be uploaded into Salesforce.

    The web application downloads a list of names+email addresses+IDs from Salesforce, joins the two lists, and returns the user's .CSV file to them in a form that is ready to "upsert" into Salesforce using standard data-loading tools.
  • With another friend's help, I wrote my first significant amount of JavaScript/JQuery. Our Web-To-Salesforce form handler wasn't doing a very good job, so I switched us out to another provider. Our forms now do a great deal of "onSubmit" pre-processing to alter the DOM before it goes off to the form handler.

    A lot of cloud form-handler providers don't let you choose your own field names, for example. They make you use names like "form143_field281." To avoid changing things like "first_name" to an obscure name/id like that in our HTML and keep our forms more portable should we have to switch form handlers again, I copy the relevant fields into hidden fields at the last minute using this JavaScript.

    There were also certain "to-Salesforce" behaviors that couldn't really be taken care of in the JavaScript or the form handler's connector, so I got to do plenty of Apex trigger programming as well.

Oh, and finally, I finished my software degree with straight A's. Booyah - time to check in about a raise!

It's been a fun period of blog "dormancy" - happy new year to all.

Tuesday, March 24, 2015

6 Ways To Code Field Value Changes / DML In SalesForce

Back when I first started designing a solution to the problem of syncing ERP "Admission" records to SalesForce "Opportunity" records, I approached it with the idea that "synchronization" means "examining both objects" for sameness or differences.

That's what I would have recommended in Oracle. We would have written a PL/SQL stored procedure or anonymous procedure with an IF (criteria) THEN (action) structure. We would have scheduled it to run as often as we expected our data to change.

 

However, I couldn't find the equivalent of a PL/SQL stored procedure - something you can invoke just because you feel like it - in SalesForce.

Sure, there were Methods in Apex Classes, but I couldn't figure out how to invoke them at will.

I wanted this functionality because a record from either object - "Admissions" or "Opportunity" - could change in a way that creates or breaks a "match" between the two objects.
It takes two to tango when you're talking about "synchronization."
Triggers (both in Oracle and in SalesForce) can only react to one type of object changing its value. A single trigger can't be progrmamed to fire based on updates to records of either object type.

 

After I posted my dilemma on the SalesForce Success Community, James Loghry of EDL Consulting provided this wonderful summary of the ways you can programmatically / batch change the value of one field based on the value of another:
(text in {}'s mine)

 

  1. Apex Triggers / {Workflow Rules} / Process Builder for handling updates / logic when a record {of a given object type} is created or updated.
     
  2. Batch Apex which can be executed manually or on a scheduled, periodic basis.
     
  3. Web Services (REST or SOAP) to update the records from either the same Salesforce instance or an external data source.
     
  4. A middleware solution (for instance Oracle Fusion / BPEL or Jitterbit) that interacts with Salesforce.
     
  5. You could also manually use tools like Dataloader / {DemandTools} to update the records manually.
     
  6. Depending on your relationships and requirements, you could potentially use formula / {rollup summary} fields between the detail records and master contact record instead of performing "DML" transactions (record creates, updates, deletes, undeletes, merges, etc.)
    {How cool would it be if there were such a thing as "SOQL fields"? *alas*}

 

Batch Apex sounds like a very promising way to "keep thinking about synchronization the way I always have in Oracle," although I'm disappointed by the language implying that you can only have 5 jobs scheduled at a time. I wonder how much functionality you can safely cram into a single job...

 

In the meantime, because our consulting partners are skilled at writing Apex Triggers, we have tried to ask ourselves where a trigger could handle most of the work. In the case of synchronizing "Admission" and "Opportunity" records, we expect that 90% of the time, changes to the "match" between records will come from changes to Admission (ERP) records.

Our department's SalesForce users will simply have to be warned that they play with certain fields on "Opportunity" records at risk of their own confusion (about why the data didn't re-synchronize itself).

 

I'm daydreaming of more, but it's a good start in a fast push to go live.

Tuesday, March 10, 2015

SOQL Return Value Data Types When Used In Apex Code

Today in class we wrote an Apex trigger that resulted in my learning some interesting quirks about data types when embedding SOQL statements into code written in SalesForce's Apex language.

"TL; DR" summary: SOQL query results can be typecast into the object in their outermost FROM, or into a list thereof, unless the outermost SELECT involves an aggregation.

 

In class, we wrote code that looks like this:

...
for(Session_Speaker__c newItem : trigger.new {
Session_Speaker__c sessionSpeaker = [SELECT Session__r.Name, Speaker__r.First_Name__c, Speaker__r.Last_Name__C FROM Session_Speaker__c WHERE Id=:newItem.Id];
.../* Do stuff */ }
...

For anyone who can't already read this:

The above code loops through each "Session Speaker" record that has recently been inserted into the database (it's an 'after insert'-typed trigger). It uses a holding variable called "newItem" to store the entirety of the record currently being processed. Each time it executes the code inside the loop, it does an SOQL query against ALL rows of the entire database stored in the "Session Speaker" object (table) and sees if any of them have an Id with the same value as the "SessionSpeaker" record currently being examined by the for-loop. If it finds such a "Session Speaker" record, it stores a specialized copy of it in yet ANOTHER holding variable, this one called "sessionSpeaker."

I say "specialized copy" because it doesn't include all of the fields of "Session Speaker" and even throws in a few extras from an object that acts as a "master" to SessionSpeaker (the fields referred to with the MasterObjectName__r.FieldFromTheMasterRecord__c syntax).

SOQL is good at making such "specialized copies" of rows from SalesForce objects - a lot like formula fields are good at making "specialized" single fields. If you're a database programmer, I'm guessing you even try to put as much of your algorithm into the SOQL parts of your triggers as you can.

 

Anyway, both "holding" Apex variables, newitem and sessionSpeaker, have a data type of "Session Speaker object" or "Session_Speaker__c."

Handling the "data type" returned from the SOQL query, as you store it in Apex, is pretty simple.

  • When you perform a SOQL query whose outermost FROM is the "Session Speaker" object, you can store it in a "Session_Speaker__c"-typed Apex variable as long as it only returns one row.
  • If it returns more than one row, you have to store the SOQL query's output into one of Apex's typed collection classes, such as "List<Session_Speaker__c>," and iterate through or aggregate the list.

 

Only not quite.

Today, Andy Boettcher taught me that if the outermost part of your SOQL query includes an aggregation in the SELECT, the query's return value's cannot be typecast into a List<YourObjectTypeHere__c>-typed or YourObjectTypeHere__c-typed Apex variable.

SOQL queries with aggregations in the outermost query insist to any Apex code waiting to capture them that they are List<AggregateResult>-typed. Not even something like AggregateResult if there's just one row in the result. Just List<AggregateResult>.

 

Here's an example of an SOQL query with an aggregation in the outermost SELECT:

[SELECT ContactId, count_distinct(OpportunityId) FROM OpportunityContactRole WHERE IsPrimary=true AND ContactID=:c.id AND OpportunityId in
(SELECT Id FROM Opportunity WHERE RecordTypeId='087C0000000KIVR')
GROUP BY ContactId]

(I'm planning to use it to drive an "is working with the admissions department?" formula field on Contact that can be exposed to other departments.)

 

List<AggregateResult> variables require for loops and processing with a .get() method. You can't just "dot-notate" the field you want to retrieve like you can with a "Session_Speaker__c"-typed variable. Sample code coming soon.

Monday, March 2, 2015

You're Going To Need A Lot Of Formula Fields

As far as I can tell, there are only two "native" ways to extract data from one part of SalesForce and make it visible in another part of SalesForce independently of INSERT/UPDATE/DELETE data-modification events:

  • Formula fields (which perform data transformations on single values - think to_number, substring, etc.)
  • Rollup fields (which perform aggregations like count/min/max on a single "column" across all or selected rows of a table - or, to rephrase in SalesForce words - which perform aggregations on a single "field" across all or selected records of an object type)
If all you need to do is display a sort of "calculated value" on some user's record-editing interface, technically you could stick it onto the page with code designed for displaying custom user interfaces.  But you have to bake your own security/permissions into such code (it makes an end-run around the rules you've set up), and as far as I know, you can't grab the value and insert it into yet more code, so I haven't looked into that option.

Roll-up fields are extremely limited - especially since you can't perform them on Formula Fields.  Which means no pre-processing values and then aggregating them in real-time (what the heck, SalesForce?!  Please log in and upvote this idea, everyone - in return, you get a million brownie points from me).

Formula fields have their limits, too - for example, the fact that they can only handle single values and have no aggregation functions mean that you can only look "up the leg" of a "crow's foot" between objects - you can't look "down to the toes."

(That said, formula fields don't care if the "up the leg" is up a "master-detail" many-to-one relationship or a "lookup" many-to-one relationship.  What's the difference?  In a master-detail relationship, the "detail" record can't exist without being associated to a "master" record.  In a "lookup" relationship, it can - unless you set the field as mandatory, but SalesForce doesn't consider that to turn it into a "master-detail" relationship.)

Still, overall, formula fields are pretty powerful.

And necessary, because it seems that SalesForce lacks a way to define criteria for automated DML based on abstract conditions that you can check for any old time you feel like it (the way Oracle Stored Procedures allow you to do).

It's my understanding that SalesForce can ONLY execute code through triggers (which, like in Oracle, fire in reaction to specific data changes - NOT to invocation by a human or other scheduled code).  You can't just store a "method" and invoke it at will.**

To avoid trigger-ing yourself into loops you lost track of at line 1874, I've found that it's best to keep as much "code" as you can in formula fields.  They might even be responsible for getting data from where a trigger put it to where the next trigger is going to pick it up from.


Coming next:
  • My presentation of a "rate yourself on a scale from 1 to 10 trillion" formula field I made to help evaluate "ranking" for "Admissions Record" objects.  This allows you to "MAX()" records based on textual data that is "better" or "worse" than other textual data - for example, ranking an "admitted" record higher than a "just applied" record.  (Which the lack of Rollup Fields for Calculated Objects makes less useful - don't forget to upvote! - but at least you have LESS code to put into a trigger.)
  • An idea I'm still working through in my spare time that involves using triggers, then formula fields to make sure that no matter where data is changed (in Admissions Records or in Opportunities, which need to be kept in sync - but I don't really care which side changed to trigger the "sync"), it always involves an INSERT/UPDATE to Opportunity, followed by a trigger on Opportunity.



**Update 7/13/16: It turns out you can store procedures and invoke them "at will." To store the procedure, you simply write a "public" method in a "public" class, write a "test class" & "test method" to make sure you have adequate "code test coverage," and put your code into production. To actually invoke your method, you can write more code (including VisualForce) to set up a "button" that invokes it, you can call it directly from the "Execute Anonymous" area of the "Developer Console" if you're a sysadmin, or you can write more code to set up a "scheduled" job that invokes it.