lukethacoder / fflib.dev

⚡ Unofficial and semi-opinionated fflib documentation - Apex Enterprise Patterns
https://fflib.dev
MIT License
5 stars 0 forks source link

Feedback for “Domain Layer: Basic Example” #3

Closed LuidyC3C closed 1 month ago

LuidyC3C commented 11 months ago

Why we need create instance a domain with a list sObject, and can't create instance a domain using a unique sObject, or with just a sObjectType equals Selector Layer ?

lukethacoder commented 11 months ago

@LuidyC3C Not too sure I follow. Can you post a code example?

LuidyC3C commented 11 months ago

For example if I want create a domain instance of PortalUser, I think that could may did like this:

List<PortalUser__c> portalUsers = new List<PortalUser__c>();
IPortalUsers iPortalUsers = (IPortalUsers) Application.Domain.newInstance(PortalUser__c.sObjectType);
Map<String, Boolean> validatedUsersBySsoId = iPortalUsers.validateUserSso(portalUsers);

Instead of

List<PortalUser__c> portalUsers = new List<PortalUser__c>();
IPortalUsers iPortalUsers = (IPortalUsers) Application.Domain.newInstance(
  portalUsers
);
Map<String, Boolean> validatedUsersBySsoId = iPortalUsers.validateUserSso(portalUsers);

Because if i understand, this list that I pass in the parameter of method newInstance not can used in my method, I need pass into method again to use.

lukethacoder commented 11 months ago

Maybe this is an issue with the example itself.

The portalUsers list you pass into the domain instance will be accessible automatically (via getRecords()). The example is showing you can overload the method to allow you to either use the portalUsers list that you used to initialise the domain or pass in a new list of portalUsers.

List<PortalUser__c> portalUsers = new List<PortalUser__c>();
IPortalUsers iPortalUsers = (IPortalUsers) Application.Domain.newInstance(
  portalUsers
);

// passes in a new list of portalUsers
Map<String, Boolean> validatedUsersBySsoId = iPortalUsers.validateUserSso(portalUsers);

// uses the initial portalUsers list that was passed as part of the domain new instance
Map<String, Boolean> validatedUsersBySsoId = iPortalUsers.validateUserSso();

This is up to you when building your domain classes.

The example domain class uses both. But you could choose to use either one or the other, or both.

// 3. Domain methods
public Map<String, Boolean> validateUserSso() {
  // getRecords() uses the context (portalUsers) passed when initialising the domain class
  validateUserSso((List<PortalUser__c>)getRecords());
}
public Map<String, Boolean> validateUserSso(List<PortalUser__c> portalUsers) {
   // run sso validation here
}

Saying all that, the idea of a domain class is that it runs methods on a list of SObjects. I assume this is why the underlying framework takes in a List<SObejct> instead of an SObjectType like you mentioned.

LuidyC3C commented 11 months ago

I understand, the first time I read it I thought the same thing as you explained.

But when I tested the hypothesis, using the following code below: (And some variations)

I was only able to receive data from getRecords in the context of the trigger, when I call using execute anonymous i receive an empty list.

Interface:

public interface Accounts extends IApplicationSObjectDomain
{
    void see();
}

Implementation:

public inherited sharing virtual class AccountsImpl extends ApplicationSObjectDomain implements Accounts
{    
    public AccountsImpl() // Needs implementation because the following error appears: System.TypeException: AccountsImpl does not have a no-arg constructor
    {
        super(new List<Account>()); // It cannot be null because it reports error: Attempt to be de-reference a null object
    }

    public AccountsImpl(List<Account> listAcct)
    {
        super(listAcct);
    }

    public override void onBeforeInsert() 
    {
        this.see();
    }

    public void see()
    {
        System.debug(getRecords());
    }
}

Script that executed:

List<Account> acctList = new List<Account>{
    new Account(Name = 'Luidy')
};

Accounts accounts = (Accounts) Application.Domain.newInstance(
  acctList
);

accounts.see();
lukethacoder commented 11 months ago

Not too sure why you're having issues. I assume your AccountsImpl Domain class is just a snippet (otherwise you're missing some of the boilerplate code).

Did a quick test with this Cases domain class

public interface ICases extends IApplicationSObjectDomain {
  void testDomainMethod();
  void testDomainMethod(List<Case> cases);
}
public inherited sharing class Cases extends ApplicationSObjectDomain implements ICases {
  public static ICases newInstance(List<Case> records) {
    return (ICases) Application.Domain.newInstance(records);
  }

  public static ICases newInstance(Set<Id> recordIds) {
    return (ICases) Application.Domain.newInstance(recordIds);
  }

  public Cases(List<Case> records) {
    super(records);
  }

  public class Constructor implements fflib_SObjectDomain.IConstructable {
    public fflib_SObjectDomain construct(List<SObject> sObjectList) {
      return new Cases(sObjectList);
    }
  }

  public void testDomainMethod() {
    System.debug('getRecords() ' + getRecords());
    testDomainMethod((List<Case>)getRecords());
  }
  public void testDomainMethod(List<Case> cases) {
    System.debug('cases ' + cases);
  }
}

Then running this the Anonymous Apex window:

List<Case> cases = new List<Case>();
cases.add(
  new Case(
    Comments = 'Test Case Name'
  )
);
ICases iCases = (ICases) Application.Domain.newInstance(
  cases
);
iCases.testDomainMethod();

Debug console output:

USER_DEBUG|[32]|DEBUG|getRecords() (Case:{Comments=Test Case Name})
USER_DEBUG|[36]|DEBUG|cases (Case:{Comments=Test Case Name})
LuidyC3C commented 11 months ago

I think that installed wrong the packages, because when I execute your code this error occurs:

FATAL_ERROR System.TypeException: Cases does not have a no-arg constructor

I deployed the repos using this commands line:

sfdx shane:github:src:install -c -g apex-enterprise-patterns -r fflib-apex-mocks -p sfdx-source/apex-mocks
sfdx shane:github:src:install -c -g apex-enterprise-patterns -r fflib-apex-common -p sfdx-source/apex-common
sfdx shane:github:src:install -c -g apex-enterprise-patterns -r force-di -p force-di
sfdx shane:github:src:install -c -g apex-enterprise-patterns -r at4dx -p sfdx-source/core

LOL , thank u very much for help me.

lukethacoder commented 1 month ago

@LuidyC3C did you manage to get this working. Your code example earlier clearly has a Constructor (with no args). Might be a deployment issue?