Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot get the correct Singleton service when with multiple DI.getModule() #1

Closed
valorad opened this issue Mar 17, 2023 · 3 comments
Closed

Comments

@valorad
Copy link

valorad commented Mar 17, 2023

When I try to get two modules with the same service interface in the same context, the latter service is not retrieved correctly.

For example, when I run this in Dev console:

// use module to resolve services
DI.Module defaultModule = DI.getModule(DefaultServiceModule.class);
IAccountService accountServiceInstance = (IAccountService) defaultModule.getService(IAccountService.class);

List<Account> defaultResult = accountServiceInstance.getAccounts();
system.debug('Default result');
system.debug(defaultResult);

DI.Module mockModule = DI.getModule(MockServiceModule.class);
IAccountService mockAccountServiceInstance = (IAccountService) mockModule.getService(IAccountService.class);

List<Account> mockResult = mockAccountServiceInstance.getAccounts();
system.debug('mock result');
system.debug(mockResult);

I get the exact same result for defaultResult and mockResult

13:18:54:080 USER_DEBUG [17]|DEBUG|(Account:{Id=001Dn00000GY6PSIA1, Name=Sample Account for Entitlements}, Account:{Id=001Dn00000GgTgHIAV, Name=Edge Communications}, Account:{Id=001Dn00000GgTgIIAV, Name=Burlington Textiles Corp of America}, Account:{Id=001Dn00000GgTgJIAV, Name=Pyramid Construction Inc.}, Account:{Id=001Dn00000GgTgKIAV, Name=Dickenson plc}, Account:{Id=001Dn00000GgTgLIAV, Name=Grand Hotels & Resorts Ltd}, Account:{Id=001Dn00000GgTgMIAV, Name=United Oil & Gas Corp.}, Account:{Id=001Dn00000GgTgNIAV, Name=Expr

While the expected result should be different.

However, when I comment out all the code for defaultResult, then this time the mockResult is as expected.

13:17:36:072 USER_DEBUG [17]|DEBUG|(Account:{Name=qaa}, Account:{Name=qab}, Account:{Name=qac})

Here are my service implementations

public with sharing class AccountService implements IAccountService {
  public List<Account> getAccounts() {
    return [
      SELECT Id, Name
      FROM Account
    ];
  }
}
public with sharing class MockAccountService implements IAccountService {
  public List<Account> getAccounts() {
    List<Account> accounts = new List<Account> {
      new Account(Name='qaa'),
      new Account(Name='qab'),
      new Account(Name='qac')
    };

    return accounts;
  }
}
public with sharing class DefaultServiceModule extends DI.Module {
  public override void configure(DI.ServiceCollection services) {
    services.addSingleton('IAccountService', 'AccountService');
  }
}
public with sharing class MockServiceModule extends DI.Module {
  public override void configure(DI.ServiceCollection services) {
    services.addSingleton('IAccountService', 'MockAccountService');
  }
}
@inksword
Copy link
Collaborator

inksword commented Mar 22, 2023

@valorad - Thanks a lot for the feedback and a very clear issue description.

Singletons are registered in a global context, once it is initialized for accountServiceInstance, its result will be cached and reused whenever there is an request to initialize the IAccountService. I haven't documented this mechanism clearly, and I am going to add this part.

The following features are not supported for singleton, and here are the reasons:

  1. Runtime Override: Why runtime singleton override is not implemented, because it could be dangerous, when each module registered its own singleton, and they loaded in different order every time, the final instance is not under control.
  2. Runtime Replacement: Usually when MockServiceModule used in test classes, the original service is not intended to be initialized. So the singleton replacement method is not necessarily provided in order to prevent abuse. I can provide one but I hesitated due to this reason. I also need strong evidence to know this is really a limitation before implementing it.

In your case, it will work if the IAccountService is registred as a scoped, or transient lifetime inside MockServiceModule .

public with sharing class MockServiceModule extends DI.Module {
  public override void configure(DI.ServiceCollection services) {
    services.addScoped('IAccountService', 'MockAccountService'); // use addScoped()
  }
}

If you have new thoughts, please just let me know. And sorry to respond you 4 days late, I am busy at a new library these days. It is published now, I think it is the best SOQL query builder you can ever find in github, wish you can also have a try https://github.com/apexfarm/ApexQuery.

@inksword
Copy link
Collaborator

inksword commented Mar 22, 2023

I have just added a new section 1.2 Singleton Lifetime Caveat, to explain the singleton mechanism behind and how to mitigate the issue.

@valorad
Copy link
Author

valorad commented Mar 22, 2023

@inksword Got it. Thanks for your thorough explanation!

@valorad valorad changed the title Cannot get the correct service when with multiple DI.getModule() Cannot get the correct Singleton service when with multiple DI.getModule() Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants