Required for a scope in an injection framework

algorithmsdesign-patternsscope

Working with libraries like Seam, Guice and Spring I have become accustomed to dealing with variables within a scope. These libraries give you a handful of scopes and allow you to define your own. This is a very handy pattern for dealing with variable lifecycles and dependency injection.

I have been trying to identify where scoping is the proper solution, or where another solution is more appropriate (context variable, singleton, etc). I have found that if the scope lifecycle is not well defined it is very difficult and often failure prone to manage injections in this way.

I have searched on this topic but have found little discussion on the pattern. Is there some good articles discussing where to use scoping and what are required/suggested prerequisites for scoping?

I interested in both reference discussion or your view on what is required or suggested for a proper scope implementation. Keep in mind that I am referring to scoping as a general idea, this includes things like globally scoped singletons, request or session scoped web variable, conversation scopes, and others.

Edit:

Some simple background on custom scopes: https://stackoverflow.com/questions/9429221/google-guice-custom-scope/9435182#9435182

Some definitions relevant to above:

“scoping” – A set of requirements that define what objects get injected at what time. A simple example of this is Thread scope, based on a ThreadLocal. This scope would inject a variable based on what thread instantiated the class. Here's an example of this:

“context variable” – A repository passed from one object to another holding relevant variables. Much like scoping this is a more brute force way of accessing variables based on the calling code.

Example:

methodOne(Context context){
    methodTwo(context);
}

methodTwo(Context context){
    ...
    //same context as method one, if called from method one
}

“globally scoped singleton” – Following the singleton pattern, there is one object per application instance. This applies to scopes because there is a basic lifecycle to this object: there is only one of these objects instantiated.

Here's an example of a JSR330 Singleton scoped object:

@Singleton
public void SingletonExample{
...
}

usage:

public class One {
     @Inject
     SingeltonExample example1;
}

public class Two {
     @Inject
     SingeltonExample example2;
}

After instantiation:

one.example1 == two.example2 //true;

Best Answer

Limit the use of scopes or more concretely Use scopes for wiring only.

Used properly, scopes can reduce a lot of factory boiler plate. I use scopes to wire together sub-processes that may need access to its name and arguments. This is similar to the RequestScopes provided by Guice and Spring.

But scopes are effectively a thread-local map of string to object. This is practically a Global Variable Depot. This is why I limit my scope usage.

This leads to the corollary:

Hide the scopes or more generally Hide your DI Framework

Because scopes (and DI frameworks) are essentially Global Variable Depots, I prefer to encapsulate the DIF such that the only thing that knows there is a DIF is the main method.

To do this, I define my own scope interface and my own ProcessFactory that I define within a Guice module.

Because of this, my process manager is free of references to Guice. In fact, it is little more than this method:

void run(final ProcessContext context) {
    try {
        this.scope.enter(context.processArgs());
        this.factory.create(args.processName());
    } finally {
        this.scope.exit();
    }
}

Here's the complete Guice module that binds and hides my use of a Guice. It implements my own ProcessScope and ProcessFactory interfaces. It binds @ProcessParameters so they can be injected and a few convienience objects as well (@ProcessName String and ProcessConfig).

public class ProcessRunnerModule extends AbstractModule {
    private static final String PROCESS_RUN_SCOPE = "ProcessRunScope";

    /**
     * Objects of type Map<String, Object> parameterized by @ProcessParameters
     * are injected in the ProcessRunScope
     */
    static final Key<Map<String, Object>> PROCESS_PARAMETERS_KEY;
    static {
        final TypeLiteral<Map<String, Object>> MAP_TYPE = new TypeLiteral<Map<String, Object>>() {
        };
        PROCESS_PARAMETERS_KEY = Key.get(MAP_TYPE, ProcessParameters.class);
    }


    /**
     * Wraps Guice scope to ProcessScope. Injects the @ProcessParameters into guice
     */
    private static class GuiceScopeAdapter implements ProcessScope {

        private final SimpleScope scope;

        @Inject @SuppressWarnings("unused")
        public GuiceScopeAdapter(@Named(PROCESS_RUN_SCOPE) SimpleScope scope) {
            this.scope = scope;
        }

        @Override
        public void enterScope(Map<String, Object> parameters) {
            scope.enter();
            scope.seed(PROCESS_PARAMETERS_KEY, parameters);
        }

        @Override
        public void exitScope() {
            scope.exit();
        }
    }

    /**
     * Processes are run and bound in @ProcessRunScope.
     */
    protected void configure() {
        final SimpleScope processRunScope = new SimpleScope();
        bindScope(ProcessRunScope.class, processRunScope);
        bind(SimpleScope.class)
            .annotatedWith(Names.named(PROCESS_RUN_SCOPE))
            .to(processRunScope);
    }

    /**
     * This wraps Processes bound via MapBinder to a ProcessFactory
     */
    @Provides @Singleton
    ProcessFactory createProcessFactory(final Map<String, Provider<Process>> processFactories) {
        log.info("Instantiating process factory", "known-processes", processFactories.keySet());
        return new ProcessFactory() {
            @Override
            public Process create(final String name) {
                return processFactories.get(name).get();
            }
        };
    }

    /**
     * ProcessRunner does not know about Guice
     */
    @Provides @Singleton
    ProcessRunner createProcessRunner(
            final ProcessScope processScope,
            final ProcessFactory processFactory) {

        return new ProcessRunner(processScope, processFactory);
    }


    /**
     * Convienience: bind a @ProcessName extracted from the @ProcessParameters
     */
    @Provides @ProcessName @ProcessRunScope
    String bindProcessName(final @ProcessParameters Map<String, Object> params) {
        return params.get(ProcessRunner.PROCESS_NAME_KEY).toString();
    }

    /**
     * Convienience: bind a ProcessConfig wrapping the @ProcessParameters
     */
    @Provides @ProcessRunScope
    ProcessConfig createParamHelper(final @ProcessParameters Map<String, Object> params) {
        return new ProcessConfig(params);
    }
}
Related Topic