apex-enterprise-patterns / fflib-apex-common

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

How would you avoid circular dependency? #381

Closed AllanOricil closed 2 years ago

AllanOricil commented 2 years ago

I have a situation where the Service method returns an Enum which is declared in the Service. In the Service Interface this Enum is also referenced. This means my Service Interface depends on the Service Implementation, and the Service Implementation depends on the interface. Because of that, now I have to deploy both interface and service class together, which is also the only way salesforce won't complain about circular dependency. But I would like to remove this problem.

public interface COM_IOrganizationService {
    COM_OrganizationServiceImpl.Environment getCurrentEnvironment();
}
public inherited sharing class COM_OrganizationService {
    private static COM_IOrganizationService organizationService;

    private static COM_IOrganizationService service() {
        if (organizationService == null) {
            organizationService = (COM_IOrganizationService) Application.Service.newInstance(
                COM_IOrganizationService.class
            );
        }

        return organizationService;
    }

    public static COM_OrganizationServiceImpl.Environment getCurrentEnvironment() {
        return service().getCurrentEnvironment();
    }
}
public inherited sharing class COM_OrganizationServiceImpl extends COM_BaseService implements COM_IOrganizationService {
    public enum Environment {
        PRODUCTION,
        STAGING,
        DEVELOPMENT
    }

    public Environment getCurrentEnvironment() {
        Organization org = COM_OrganizationSelector.newInstance().selectOneElevated();

        String host = URL.getSalesforceBaseUrl().getHost();
        String server = host.substring(0, host.indexOf('.'));

        if (org.IsSandbox) {
            if (server.contains('staging')) {
                return Environment.STAGING;
            } else {
                return Environment.DEVELOPMENT;
            }
        } else {
            return Environment.PRODUCTION;
        }
    }
}

Possible Solutions:

1 - I would move these constants that seems to be related to SObjects to inside the Domain. This way my Service Interface would be dependant on the Domain, and the circular reference would be gone. But it seems weird to write COM_Organization.Environment as a return value.

2 - Another common suggestion is to declare a class to hold every Constant or Enum. But I don't feel confortable having a global class holding all my constants because I fell like constants always look better when they are close to were they are really used on. I would be losing the context of why this constant was created for. And to learn its context I would have to Search where they are used.

3 - Another thing I thought about is to declare that Enum in its own separate class, like COM_OrganizationEnvironment. This way the context of that Enum would be preserved, and no circular dependency would exist.

For me, 1 and 3 would be the choice. So what do you think? Which strategy would you go for?

The main goal of this question is to learn, as it seems natural to place these Custom Types inside the Service, which would then result in a circular dependency problem.

ImJohnMDaniel commented 2 years ago

@AllanOricil, in this case, I would simply move the enum out to its own class and not make it an "inner class enum" of the service. This way you separate the enum constant for the elements using relying on it. Hope this helps.