apex-enterprise-patterns / at4dx

Advanced Techniques for Salesforce DX Adoption Framework
BSD 3-Clause "New" or "Revised" License
121 stars 52 forks source link

Selector Layer / question about injecting method or criterias #71

Open alanjaouen opened 1 year ago

alanjaouen commented 1 year ago

Hi @ImJohnMDaniel @stohn777 ,

I'm starting to implement AT4DX in my company, and this framework help us a lot for switching to unlocked package, so first of all thank you for sharing this with us.

I'm wondering about a use case for splitting my monolithic code in package:

Let's says that:

I would like to define in packageB a methode like AccountSelector.getAccountByPackageB_id__c(Set<String>)

I did not find any exemple about this use case, should I create an AccountSelector specifically for PackageB?

Thanks for your time,

Best Alan

ImJohnMDaniel commented 1 year ago

G'day @alanjaouen. Thanks for reaching out.

A couple of questions for you

Based on what I have heard you describe, it sounds like a good candidate for the AT4DX feature "Selector Method Injection", but let me know the answers to questions above and we can go from there.

alanjaouen commented 1 year ago

Hi @ImJohnMDaniel ,

Thanks for your quick reply on this,

Have you seen the various videos about using the AT4DX framework as well as the VirtualDreamin2020 talk - "Architectural Considerations to Implementing DX and 2nd Generation Packaging"?

I based my implementation on the wiki, and these videos:

I will have a look at "Architectural Considerations to Implementing DX and 2nd Generation Packaging"

Would PackageA be placed in a "business layer" package or in a "common layer" package?

PackageA is in common layer.

Based on what I have heard you describe, it sounds like a good candidate for the AT4DX feature "Selector Method Injection", but let me know the answers to questions above and we can go from there.

I searched for this particular feature (before creating this issue), but didn't found anything online, is there any centralized documentation that reference this feature?

Thanks Alan

ImJohnMDaniel commented 1 year ago

Ok. If PackageA is a common layer package then you are good.

There are two things that you are trying to do:

Referenced examples are found in the AT4DX Sample Code repo.

Add a new field form PackageB to the PackageA.AccountsSelector -- "Selector Field Injection"

Selector Field Injection allows you to setup a field set in PackageB, and then include this new field that is only present in PackageB -- packageB_id__c. You then need to find the Custom Metadata Type object "Selector Config - Fieldset Inclusion" and add a CMDT record that binds the PackageB.fieldset to the PacakgeA default selector's SObject. That instructs the default selector for the SObject to include the additional fields specified in the fieldset.

In the AT4DX Sample Code repo, there is a new "Slogan" field added to the Account Sobject (e.g. equivalent to your PackageB packageB_id__c). That field was added to a fieldset called "SelectorInclusion_AccountFieldsMarketing". Now when the default Account selector for AT4DX Sample Code runs , the Slogan field is also included.

Add a new method to the PackageA.AccountsSelector that queries by this new PackageB field. -- "Selector Method Injection"

Selector Method Injection allows you to write a distinct class for this new method and, when the selector is called, this method class will be injected into the base selector class and operate as if it were simply a method that is directly part of the class itself.

In the AT4DX Sample Code, we had a need to query for Accounts where the Slogan field matched a specific string set. Obviously, since the Slogan field is not part of the base package (in your case, PackageA), we could not add the method directly to the AccountsSelector in PackageA. Instead, the "SelectBySloganSelectorMethod" class was created. This Selector Method class extends the AbstractSelectorMethodInjectable class and implements the ISelectorMethodInjectable interface. That contract requires the setup of the List<SObject> selectQuery() method where you can define the method that you need. This selectQuery() method behaves just like a normal selector method. That method has access to the base selector's newQueryFactory() method as well as all of the fields defined by the selector's field contract. You add fields to the base field contract and do pretty much anything that you need.

The only difference from a normal selector method is around the question of "how to pass parameters to the query class?" To do that, you define an inner class -- typically named "Parameters" -- that implements ISelectorMethodParameterable interface. You place whatever parameters that you will need for the query inside the Parameters class. Leading up to the selector method call, you would instantiate the Parameters class, add the input (for this AT4DX example, see the Parameters class sloganNameSet class variable) and then pass this along with the Selector Method class.

You can see an example of how this Selector Method is called in the AccountSloganRelatedTest class; starting at line 76. The Parameters class is setup like the following:

// Setup the injection parameter class
SelectBySloganSelectorMethod.Parameters queryParams = new SelectBySloganSelectorMethod.Parameters();
queryParams.sloganNameSet = new Set<String>{ slogan };

And the Selector Method is combined with the base AccountsSelector by using the selectInjection() method as seen on line 96

List<Account> accountsQueried = AccountsSelector.newInstance().selectInjection( SelectBySloganSelectorMethod.class, queryParams);

Hopefully this addresses your question. Let me know if something is still not clear or if I am not understanding your question correctly.

Hope this helps!

Cheers!

alanjaouen commented 1 year ago

Thanks for your complete anwer, it will definitly help me :+1: