0

I have a utility/constants class that contains a Map<String, Authorizer> where Authorizer is an interface with a few different implementations. I use the map across different streams to take in an object that contains some String (the name of an Authorization approach) that then maps to a specific Authorizer, which then completes some authorization.

I am using Guice to wire the Authorizer classes, but this approach prevents me from making the utility class (which contains the Map) a true utility class with an empty private constructor. I have a funky-looking workaround that pleases both Guice and Checkstyle, but I want to know if there is a better way.

My Utility Class:

public final class Constants {
  @Inject
  private Constants() {}

  public static final String AUTH_METHOD_ONE = "Auth1";
  public static final String AUTH_METHOD_TWO = "Auth2";

  @Singleton
  @Inject
  @Getter // For Checkstyle, as AUTH_METHODS isn't a true static final constant
  private static Map<String, Authorizer> authMethods;
}

My Constants Module:

public class ConstantsModule extends AbstractModule {
  @Override
  public void configure() {
    requestStaticInjection(Constants.class);
    final MapBinder<String, Authorizer> mapBinder = MapBinder.newMapBinder(binder(), String.class, Authenticator.class);
    mapBinder.addBinding(AUTH_METHOD_ONE).to(MethodOneAuthorizer.class);
    mapBinder.addBinding(AUTH_METHOD_TWO).to(MethodTwoAuthorizer.class);
  }
}

And an example usage:

public class AuthorizationOrchestrator {
  private static Authorizer getAuthorizer(final AuthorizationState state) {
    return state.getMethods().stream()
      .map(AuthorizationApproach::getAuthorizationApproachName)
      .filter(Constants.getAuthMethods().keySet()::contains)
      .findFirst()
      .map(Constants.getAuthMethods()::get)
      .orElse(null);
  }
}

This approach also requires some PowerMock usage in the Unit Tests. Is there a better way to:

  • Map the names of the authorization approaches to an Authorizer class while keeping the mapping in one place?
  • Use the Constants class as a true utility class with public static final Map<String, Authorizer> AUTH_METHODS while still being able to inject the authorizers into the Map?

1 Answers1

1

It doesn’t really make sense to inject something that is supposed to be a constant.

By using a static Constants class, you are introducing a non-injected dependency into the code that uses your Constants class, which goes against the whole point of DI—not to mention that static dependencies are more difficult to mock in your tests.

There’s a few other ways to do this that might work for you. In principle, they’re all the same solution (inject the Authorizers into something that is not static), but they have a few different nuances.

  • Use composition to avoid needing a utility class. Make an Authorizer implementation that is composed of multiple Authorizers. The composite Authorizer could internally delegate to the correct “real” implementation. Then you can just inject a single Authorizer anywhere that needs one. This on might not be possible depending on how the Authorizer contract is defined.

  • Keep the logic without any state in the utility class. Change the signature of the static method togetAuthorizer(AuthorizationState state, Map<String, Authorizer> availableAuthorizersByName). Then, inject the AUTH_METHODS map directly into the classes that would be calling your static getAuthorizer method, and pass the map as one of the arguments to the method.

  • Make getAuthorizer() not static. And inject the map directly into an instance ofAuthorizationOrchestrator.

Matthew Pope
  • 7,212
  • 1
  • 28
  • 49