fflib-apex-common icon indicating copy to clipboard operation
fflib-apex-common copied to clipboard

A common criteria based filter for Domains and Selectors

Open wimvelzeboer opened this issue 4 years ago • 4 comments

A common criteria based filter for domains and SOQL condition generator for Selectors.

Often the same filters are repeated in the domain and the selector classes. The Criteria feature provides a solution for that by extracting the filter conditions into a single reusable criteria class. The filter conditions are made dynamic so that they can be evaluated in run-time, or be converted to a SOQL statement condition.

                + - - - - - - - - +
        + - - - | Filter Criteria | - - - +  
        |       + - - - - - - - - +       |
        |                                 | 
        |                                 |
+ - - - - - - - +                 + - - - - - - - +
|    Domain     |                 |    Selector   |
+ - - - - - - - +                 + - - - - - - - +

Here is an example on how its used:

The criteria class is the place where all the filter conditions are stored for a single SObjectType.

public with sharing class AccountCriteria extends fflib_Criteria
{
    public AccountCriteria ShippingCountryEquals(String countryName)
    { 
        equalTo(Schema.Account.ShippingCountry, countryName);
        return this;                
    }
    
    public AccountCriteria NumberOfEmployeesGreaterThan(Integer numberOfEmployees)
    {
        greaterThan(Schema.Account.NumberOfEmployees, numberOfEmployees);
        return this;
    }
}

How it can be applied in a Domain class:

public with sharing class Accounts
        extends fflib_SObjectDomain
        implements IAccounts
{
    private static final Integer LARGE_ACCOUNT_EMPLOYEE_NUMBERS = 500;

    public Accounts getByCountry(String countryName)
    {
        return new Accounts(
                getRecords(
                        new AccountCriteria().ShippingCountryEquals(countryName)
                )
        );
    }
    
    public Accounts getByNumberOfEmployeesGreaterThan(Integer numberOfEmployees)
    {
        return new Accounts(
                getRecords(
                       new AccountCriteria().NumberOfEmployeesGreaterThan(numberOfEmployees)
                )
        );
    }
    
    public Accounts getByLargeAccountsInCountry(String countryName)
    {
        return new Accounts(
                getRecords(
                       new AccountCriteria()
                               .ShippingCountryEquals(countryName)
                               .NumberOfEmployeesGreaterThan(numberOfEmployees)
                )
        );
    }
}

In this example we see three filters; one for country, another for checking minimal number of employees and a third that combines the first two. It is important not to have a filter with too many conditions. One filter criteria condition per method is ideal to have maximum flexibility and a high chance on code-reuse.

How the same filters can be used in the Selector class:

public with sharing class AccountsSelector
        extends fflib_SObjectSelector
        implements IAccountsSelector
{
    ...
    public List<Account> selectByCountryWithMinimalNumberOfEmployees(String country, Integer minimalNumberOfEmployees)
    {
        return (List<Account>)  Database.query(
                newQueryFactory()
                        .setCondition(
                                new AccountCriteria()
                                        .ShippingCountryEquals(country)
                                        .NumberOfEmployeesGreaterThan(minimalNumberOfEmployees)
        );
    }
    ...
}

With this change developers can avoid a lot of code duplications. Hope you like it!


This change is Reviewable

wimvelzeboer avatar Jan 11 '21 16:01 wimvelzeboer

@wimvelzeboer
We discussed the PR during our monthly meeting, and although we like the direction, we're going to hold this PR because we're planning a conversation with @capeterson this month where we hope to kick-off a redesign of the query builder feature. cc. @daveespo @ImJohnMDaniel

stohn777 avatar May 25 '21 17:05 stohn777

@stohn777 Ok, thanks for the heads-up! Looking forward to seeing the redesign of the query builder. :-)

wimvelzeboer avatar May 26 '21 07:05 wimvelzeboer

@wimvelzeboer We discussed the PR during our monthly meeting, and although we like the direction, we're going to hold this PR because we're planning a conversation with @capeterson this month where we hope to kick-off a redesign of the query builder feature. cc. @daveespo @ImJohnMDaniel

Did this happen at all? Any output to share?

As a general point of feedback to the team: It'd be nice to see some more roadmaps or thinking on where to take a few of these features. It feels, to me at least, a lot like the current situation of this repository is open source, but closed behind the scenes decision making on features - with very limited ability to influence it by the community. The promises of "we're working on something" and "we have something planned to talk about the direction we want to take" with then no response for months is frustrating. @ImJohnMDaniel @daveespo @capeterson @stohn777

JAertgeerts avatar Jun 07 '22 10:06 JAertgeerts

Hi @JAertgeerts !

At this time, we're working with Salesforce to update the Trailhead module for AEP. It has diverged from the current state of AEP, especially with regard to the Domain aspect. This is a joint effort so promising a timeline for the new module to be published is not something we can do, but we do hope it's finished by Sept 1.

Separately, we have a plan for offering optional support for User Mode (Summer'22 Beta) in QueryFactory, SOUOW and Selector. This summer (i.e. before Sept 1) there will be a PR opened for community review and feedback.

I'm sure you know that AEP is a volunteer project and we maintain the project during our "free time". We appreciate all of the great ideas and PRs that the community has submitted, but each one requires careful review (for completeness of implementation, backwards compatibility, maintainability, and testability). Unfortunately the query builder topic is one that require a fair amount of review and we just flat out haven't had the time to do it. It has not been forgotten, it's just not the top priority.

daveespo avatar Jun 09 '22 14:06 daveespo