apex-enterprise-patterns / fflib-apex-common

Common Apex Library supporting Apex Enterprise Patterns and much more!
BSD 3-Clause "New" or "Revised" License
911 stars 517 forks source link

Would you have one Selector of a particular object for your entire Org, or would you create one per App/Process/Functionality in a Mono Repo Project #373

Closed AllanOricil closed 1 year ago

AllanOricil commented 3 years ago

Would you have one Selector of a particular object for your entire Org, or would you create one per App/Process/Functionality?

At the moment we are creating one seletor per object and I dont really think this is the best way of using fflib because we would endup with different apps/process/functionality being tightly coupled with that one particular selector. My view is that even if we are not creating an app or functionality that will endeup being a managed package, we should always decouple its classes and Metadata. The reason for that is to make it easy to remove it entirely in case we don't need it anymore. If we put everything on a single Selector, later we will have to edit that Selector just to be able to remove the app/functionality/process. But doing this way we will kind have code repetition, because one app might use the same data, fields and access level that the other app is using. But this repetition can be treated using Inheritance. We could have one common Selector, and each app would have its own Selector extending that common one. But because this not documented, I would like to confirm that this is a good idea.

I'm asking it because I noticed that a particular functionality that I created could be turned into a fflib app, even if I'm not going to turn it a managed/unlocked package. It looks really cleaner to have all of its resources (selectors, domains, trigger handlers, application, controllers) in its own separate app and folder, rather than having a big common Application (fflib application class). It makes everything so easy to find and delete the entire functionality in case we don't need it anymore.

This is the way Im thinking about using fflib when working in a Mono Repo Project

image

So what do you think?

AllanOricil commented 3 years ago

@wimvelzeboer @ImJohnMDaniel

ImJohnMDaniel commented 3 years ago

@AllanOricil, in short, yes, the prescribed way is to manage one selector per sobject. Having said that, if you have multiple apps (and more importantly, you will move these apps to one or more packages), you will get into a bind with FFLIB because the Application factory class in a standard FFLIB implementation statically compiles all of the bindings. This is where I would recommend using AT4DX. It allows for ways to inject new fields to a selector when the field is added to the SObject by another package (e.g. Selector Fieldset Injection). It also allows for "Selector Method Injection" when you need to add new queries to the base selector by the query references metadata elements that are not part of the package that owns the selector.

AllanOricil commented 3 years ago

@ImJohnMDaniel So if I'm in a "Mono Repo" and I cant have a separate sfdx project for that particular functionality, I must use ONE SINGLE RESOURCE (domain, Selector, triggerHandler, service) per Object.

But do you see a problem having the structure of the image I showed above? Because I'm on a mono repo, it seems so much easy to remove that entire thing if I put everything on its own folder. If I need to delete it, I can just delete that folder.

The problem I see with one single Selector (or domain, service, triggerhandler) in the Mono Repo is that its class will endeup getting huge, or APP A will query unnecessary fields or have methods that only APP B needs, and this will bind APP A with APP B. But I don't see any problems with the structure I showed above.

ImJohnMDaniel commented 3 years ago

If you are in a mono repo, that is fine. I usually go with the "one repo for one app" model for various reasons, but you don't have to. You can have "one repo for multiple apps" and separate the repos by directory structure (there are some nuances that can trip you up with this approach, so be mindful).

The structure on the image above doesn't concern me so much. Just be aware that when you setup a scratch org, SFDX CLI will push all directories to the scratch org. You might run the risk of getting metadata references that "cross the app boundaries" and you won't catch it until a package version build. A very strict "prefix naming convention" will help keep things organized between application boundaries. (This issue is one of the main reasons why I go for a "one repo for one app" model.)

You concern about selectors and domains getting huge with multiple app concerns is valid. Again, AT4DX can help here and was designed for this vary purpose. The origins of AT4DX predate the advent of SFDX/2GP but when SFDX/2GP came around, the techniques easily transition into covering those use cases as well.

AllanOricil commented 3 years ago

@ImJohnMDaniel this is another good reason for not having one single Selector for everything on your org

https://help.salesforce.com/s/articleView?id=000333028&type=1

If selector methods use newQueryFactory(true) and a Selector has all the fields declared in the getSObjectFieldList(), these methods have a risk of achieving this SOQL size limit.

Also, if App B and App A both use newQueryFactory(true), App A will query fields it does not care, and App B will also do the same, and it impacts the Performance.

So, I think we can safely conclude that it is best to separate App A' Selector from App B's Selector. And if the dev wants to centralize the Access Level, he can have a common Selector which A and B Selectors will extend from and inherit the access level.

ImJohnMDaniel commented 3 years ago

Well, it is important to remember that the Selector Pattern does not call for a selector class to always have every field on an SObject. Instead, the Selector Pattern defines what the minimum constitution should be for a SObject record. It forms the basis of the contract "for what the SObject record should be guaranteed having queried". It does not specify the contract that "every field will be queried."

Your reference is definitely one of those reasons to not specify all fields. There is a maximum length to a SOQL query and specifying every field on large objects could easily run into that limit. Another reason not to specify all fields on the Selector is the overhead involved with Long Text Area and Rich Text Area fields. Those fields incur a lot over memory overhead when most of the time, those fields are not required in the transaction. Better to use the query factory's "selectField" method to add them to the minimal contract of fields when needed at the selector method level.

The other issue with multiple selectors is that you lose out on the benefit of a single point of reference and a single point of maintenance by specifying more than one selector. In multi-selector scenarios, your selector-clients need to be more intimately aware of which selector they need for which purpose. If you have multiple selectors, then you need to manually keep them consistent with the same "minimal contract of fields." In large organizations, many different processes could interact with the SObject records. Having them originate from one of many sources runs the greater risk of encountering SObjectField Exceptions where the field is referenced but not first queried.

The last concern that I can think of is that the Application Factory can't uniquely identify the selector to use for an Sobject. This is, of course, assuming that you are using an Application Factory for the Selectors. I highly recommend doing so as it sets the codebase up perfectly to swap in ApexMocks where ever needed and promotes a more loosely coupled codebase.

The prescriptive advice on this question definitely has been "one selector per SObject" and, while you need to be mindful of the topics mentioned above, it has definitely been a pattern that has proven success in countless implementations.

AllanOricil commented 3 years ago

@ImJohnMDaniel I still think that one selector per application brings so many benefits and no cons for a Mono Repo Project. (consider it one that does not use packages and many sfdx projects, because we can't not because we don't want)

PROS: 1 - I can easily see which fields that particular app uses, declaring a getSObjectFieldList() with all fields that a particular APP may care about. It does not mean Im going to use all of them on every Selector method, it is just there so a developer can quickly see which fields matter to the App. This can also be used to guide the developer to create a permission set and put only those that the APP care for that Object. 2 - I can remove the Entire App resources knowing I would not damage any other App (decoupled apps). If App A and B use the same Selector, and I need to remove App B, I will have to edit the Selector to remove methods or fields that App B was using, just to clean things. 3 - Developers will not Accidentally remove a method or a field reference from getSObjectFieldList() wich would then impact a method that is using "newQueryFactory(true)". Like, imagine both App A and B use the same method, and both use newQueryFactory(true). Now Imagine someone accidentally removed one field that App B needed. This situation will not throw an exception unless someone test App B or it was covered on an Apex Test. And you know not everyone or organization spends time writting good tests. So this may happen in case we use a Single Selector two Apps share.

CONS: I dont see any. You can list the ones you think and then we leave it here at the end so other people can take their own conclusions.

And we can extend it to all the other resources of an Fflib Application (domains, trigger handlers, selectors, services).

ImJohnMDaniel commented 3 years ago

G'day @AllanOricil, regarding your points.

You are definitely trying to solve some common items that are exhibited when you try to separate the org into multiple apps. Again, AT4DX was designed to address all of these points without leading to duplicates. But if you are not going to packages, you may not need it if you can enforce a rigged set of standards with your developers.

Regarding the cons, the points that I mentioned earlier are still valid and should be actively monitored by your team. Experience has shown that they are pitfalls that a development team can easily fall into.

Question: When you say "...extend it to all the other resources of an Fflib Application (domains, trigger handlers, selectors, services)," what specifically are you referring to? Having multiple domains on an SObject; one per app? And I am not sure what would be extended for services as they would never be duplicated across apps because they tend to be "application specific".

AllanOricil commented 3 years ago

@ImJohnMDaniel I will start studying this AT4DX. How can I get started with that? Do you have a video that would help me?

ImJohnMDaniel commented 3 years ago

@AllanOricil, So, AT4DX is another framework repo hosted here on the Apex Enterprise Patterns Github Organization.

Probably the quickest way to get up to speed on it is the Dreamforce18 presentation "Advanced Techniques To Adopt Salesforce DX Unlocked Packages"

Start with that and let me know if you have more questions.