4

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;
8
  • 2
    I've been asking myself the same for years. All those scopes, and even worse, the in-jection/out-jection mechanisms (à la Seam) are no different from spaghetti programming. Bijection of variables is the new goto. Mar 21, 2012 at 17:09
  • I fear the question is overly broad and not a good fit for SO. Maybe better for Programmers. Here, I don't see an objective correct answer. Mar 21, 2012 at 20:20
  • Im open to having this question moved over to Programmers. Mar 21, 2012 at 21:02
  • Oscar Lopez: To the contrary, I think that injection (I never use @Out btw) can clean code up significantly and can aid in testing. I have found I am writing less and less plumbing code and focus on the buisness of the applicaiton. As always, it should be used judiciously and could lead to some pretty horrible designs. Mar 21, 2012 at 21:04
  • I was cautious posting here as I hate reading questions that have no concrete answers on SO... I think my concrete question is embodied in paragraph 3: References to scoping discussion and/or what are prerequisites for a scope. Mar 21, 2012 at 21:07

2 Answers 2

2

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);
    }
}
2
  • Great example. I like the encapsulation of the scope configuration within the module. Would you say that a good requirement for a scope is a clear starting and ending point then? Apr 25, 2012 at 17:00
  • That's the only way I use them. But, I've also seen them used to wire things together per location, or similarly, per website. Apr 25, 2012 at 18:38
0

Do not inject objects that have a smaller scope then the object you inject it into. In that case inject a factory so the dependant can manage the scope.

All other scope can be handled through the DI framework. The DI framework should be able to query some object or method you provide to see if it should re-instantiate a new instance or not.

In other words, a DI framework should delegate complex scope management to you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.