+ Start a Discussion
Vladimir BessonovVladimir Bessonov 

Apex data structs

Hello. 
I want to use Apex trigger and Apex class to add records to the record that was just inserted 

trigger CreatePropSet on Propulsion__c (after insert) {
    PropulsionSet.addPropSet();
}

PropulsionSet consists of the propulsion parts. 

in JavaScript I would create and object that I used as a template, in C/C++ - struct. What shall I use in Apex? Where to keep not changing data? in the same class PropulsionSet? 
 
Alain CabonAlain Cabon
Hi Vladimir,

The state of the art here would be to use a class for the domain layer (Propulsions) and a "generic" trigger like below (always the same content excepted for the domain class (Propulsions.class)):

https://github.com/apex-enterprise-patterns/fflib-apex-common
 
trigger PropulsionTrigger on Propulsion__c (
  after delete, after insert, after update, after undelete, before delete, before insert, before update) {
   // Creates Domain class instance and calls appropriate methods
   fflib_SObjectDomain.triggerHandler(Propulsions.class);
}

You are using the injection ( fflib_SObjectDomain.triggerHandler(Propulsions.class);) and all the treatments are into the domain layer (Propulsions.class) that will implement the custom logic according the content of the Trigger.new/Trigger.oldMap at runtime.
 
public class Propulsions extends fflib_SObjectDomain {
    public Opportunities(List<Propulsion__c> sObjectList) {
        super(sObjectList);
    }
    public class Constructor implements fflib_SObjectDomain.IConstructable {
        public fflib_SObjectDomain construct(List<SObject> sObjectList) {
            return new Propulsions(sObjectList);
        }
    }
   .....
}

That works because there is a context for the trigger that is always the same like below (true/false, empty/defined) so it is useless to pass these parameters to the triggerHanlder (inherited context from the trigger that calls the method triggerHandler).
// Process the runtime Apex Trigger context 
triggerHandler(constructor, 
				Trigger.isBefore, 
				Trigger.isAfter, 
				Trigger.isInsert, 
				Trigger.isUpdate, 
				Trigger.isDelete, 
				Trigger.new, 
				Trigger.oldMap);

private static void triggerHandler(IConstructable domainConstructor, Boolean isBefore, Boolean isAfter, Boolean isInsert, Boolean isUpdate, Boolean isDelete, List<SObject> newRecords, Map<Id, SObject> oldRecordsMap) { ... }

https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_dsl/apex_patterns_dsl_apply_dl_principles

https://trailhead.salesforce.com/content/learn/modules/apex_patterns_dsl/apex_patterns_dsl_learn_dl_principles

It is not easy at first but it is the most elegant way to write triggers according the Apex Enterprise Patterns according the Separation of Concerns of Martin Fowler’s Enterprise Architecture Patterns.
 
Vladimir BessonovVladimir Bessonov
nice. But I am new to Salesforce, I dont think it is good idea start with domain level... and it does not answer where I would better store my static values for the class I plan to use. 

I was thinking to use static class method for generating the objects. 

public class PropulsionSet {
  
    List<String> PartNames = new List<String> {'Resistor','Motor','TIC'};
           
   public void addPropulsionSet() { // I need to pass   Propulsion__c.Id how? Trigger context? 
       
        for(Integer i=0; i< PartNames.size();i++){
    
        // List<Propulsion_part__c> PartList = New List<Propulsion_part__c>; 
        }       

insert PartList;
}
}

trigger CreatePropSet on Propulsion__c (after insert) {
    
    PropulsionSet propSet = new PropulsionSet();
    propSet.addPropulsionSet(); // how to pass the ID of Prop? Propulsion__c.Id
    System.debug('part added');
}

Thanks for state of the art, but I cannot consume it right now. 
Alain CabonAlain Cabon
fflib-apex-common is suitable for big projects when these demanding patterns are decided at the beginning (promoted by Salesforce on trailhead)

The open source project NPSP of Salesforce also uses a principle quite similar but they pass all the parameters (less simplified for the call but simplified for the overall classes and code).
https://github.com/SalesforceFoundation/NPSP/tree/master/src/triggers
 
trigger TDTM_FormTemplate on Form_Template__c (before insert, before update, before delete, after insert,
        after update, after delete, after undelete) {
    TDTM_Config_API.run(Trigger.isBefore, Trigger.isAfter, Trigger.isInsert, Trigger.isUpdate, Trigger.isDelete,
            Trigger.isUndelete, Trigger.new, Trigger.old, Schema.SObjectType.Form_Template__c);
}
 
trigger TDTM_Opportunity on Opportunity (after delete, after insert, after undelete, 
after update, before delete, before insert, before update) {

    TDTM_Config_API.run(Trigger.isBefore, Trigger.isAfter, Trigger.isInsert, Trigger.isUpdate, Trigger.isDelete, 
        Trigger.isUnDelete, Trigger.new, Trigger.old, Schema.Sobjecttype.Opportunity);
}

The TDTM (Table-Driven Trigger Management) configuration:

Most users (probably more than 90%) have never heard of TDTM or have any understanding of why it matters to them. There is a fantastic technical article written by Carlos Ramirez Martinez-Eiroa (now working on Higher Ed Data Architecture, which also implements TDTM) that walks you through the technical details of this amazing feature. I’m here to elaborate on it a bit more, and explain why this super-technical-sounding thing is so great.

https://www.salesforce.org/blog/table-driven-trigger-management-matters/

In reality, few people can follow these "ideal" principles (90% have never heard of them) above all when the project is already almost completly coded or quite small ( I don't use them neither excepted for the support here ).

The Salesforce documentation of Apex never use fflib-apex-common nor TDTM.

 
Vladimir BessonovVladimir Bessonov
Coming back to simple staff. 

I have trigger and I have a class. 

Now in the class I have several Lists that contains names, material numbes, suppliers, etc.
I iterate over the list and create new object 

public void addPropulsionParts( ID PropID) {  
  
       for(Integer i=0; i< PartNames.size();i++){
        Propulsion_part__c part = new Propulsion_part__c(Propulsion__c=PropID, Name=PartNames[i], 
        Material_No__c=PartMaterialNumber[i],
        Supplier_Part_No__c=PartSupplierPartNumber[i], 
        Supplier__c=PartSupplier[i]);
    
        PartList.add(part);
        }  ... 

I keep in class enormous lists. Any way to restructure it? Like save those lists in one object for example, import this object into the class from other file, etc? 

To me it looks urgly. 
 
Alain CabonAlain Cabon
You can perhaps use a creational GoF pattern.  

Justin Lyon has created all the examples of use with Apex
https://github.com/alcabon/apex-gang-of-four/tree/master/creational

The idea is often to create one method for each attribute to add (not shorter in code but more easy to use with ).
 
public class PropulsionParts {
    
    private String Material_No;    
    private String Supplier_Part_No;
    
    public String getMaterialNo() {
        return this.Material_No;
    } 
    public String getSupplierPartNo() {
        return this.Supplier_Part_No;
    } 
    public PropulsionParts addMaterialNo(String materialNo) {
        this.Material_No =  materialNo;
        return this;
    }
    public PropulsionParts addSupplierPartNo(String supplierPartNo) {
        this.Supplier_Part_No = supplierPartNo;
        return this;
    }
}

You can write:

PropulsionParts p = new PropulsionParts();

p.addMaterialNo('12345').addSupplierPartNo('7890');
// one line , not possible with setters, in java, there is the autocompletion so this composition of fields are quick to do


system.debug('material:' + p.getMaterialNo());
system.debug('supplier:' + p.getSupplierPartNo());

or

PropulsionParts p = new PropulsionParts();
p.addMaterialNo('12345'); // equivalent to a setter
p.addSupplierPartNo('7890');
system.debug('material:' + p.getMaterialNo());
system.debug('supplier:' + p.getSupplierPartNo());

It is a kind of builder of object easy to understand. You have hundreds of fields so there is not a big difference or even not suitable.