Guice puzzler: Batch scoped Encapsulated Context

2019-07-07 04:48发布

问题:

We are preparing to begin using Guice in our insurance data conversion platform and I have encountered an interesting scenario that does not seem to be directly addressed in the Guice docs or any postings I have found.

Our platform uses the Encapsulated Context (EC) pattern in several important areas. For example, imagine we are processing a set of 10 policies. Whenever we begin processing a new policy, we wish to construct a PolicyContext object and initialize properties such as policy number, state, and company. This PolicyContext is a dependency for many classes that are involved in the conversion process.

Note that PolicyContext (and other *Context objects within our app) is a value object that is tightly focused in a specific domain area (representing basic, ubiquitously needed policy information). I would be interested to know whether the pattern gurus among you still consider this to be an anti-pattern (as discussed by Misko Hevery in http://misko.hevery.com/2008/07/18/breaking-the-law-of-demeter-is-like-looking-for-a-needle-in-the-haystack/ ) even though these are purely value objects and certainly don’t represent the “kitchen sink.”

Currently, we are managing PolicyContext in the worst possible way: we have a static global variable, policyContext, and policyContext.initialize(String company, String state, String policyNum) is called whenever we start processing a new policy.

My goal is for Guice to manage these context objects in an architecturally optimal manner so that, conceptually, whenever we begin processing a new policy:

  1. Guice discards the old PolicyContext.
  2. Guice construct a new, immutable PolicyContext (no smelly initialize method) using company/state/policyNum params coming from a database.
  3. Guice injects the already constructed PolicyContext into all the classes that require it.

Here is my tentative approach:

  1. Create a custom scope—something akin to the Guice batch scope sample at http://code.google.com/p/google-guice/wiki/CustomScopes--where the boundaries of the batch are externally determined. With this scope, where we begin processing a new policy, we can 1) end the previous “batch” and begin a new one. Q: Any reason I can’t use the Guice batch scope sample exactly as listed at the aforementioned URL?
  2. Since PolicyContext has no dependencies, we would use AssistedInject for all constructor parameters (which seems a bit odd). Assuming we take that approach and generate a PolicyContextFactory, it follows that where we start processing a new policy we would have code such as:

    …
    scope.exit();
    scope.enter();
    @Inject private PolicyContextFactory policyContextFactory; 
    policyContextFactory.create(company, state, policyNum); // the parameters come from a database record.
    // Note that we don’t need to actually store the created instance; it will be injected elsewhere into various class constructors.
    …
    

Does this seem optimal? I know there may be simpler approaches (e.g. creating a new, PolicyContext specific injector, whenever we process a new policy, which effectively creates a new PolicyContext). However, this is a core aspect of the architecture, so I really don’t want to compromise.

Another option, I know, would be to abstain from using DI in this scenario and just use a static PolicyContextManager class with separate create and get methods, where the former method is the factory that discards the current PolicyContext and creates/stores a new one, while the latter method simply returns the “active” PolicyContext). But my code will just end up doing manual DI because I’ll be writing lots of code like methodThatNeedsPolicyContext(PolicyContextManager.get(), …). Since we intend to start using Guice anyway, this approach doesn’t seem to be optimal.

BTW, for those attempting to cultivate a deeper understanding of DI, I highly recommend “Dependency Injection” by Dhanji Prasanna. This book, which focuses on Guice and Spring, was absolutely indispensable, as it goes so much deeper than anything else I have encountered.

Thanks for your help!

回答1:

It seems your linked SimpleScope is almost perfect for your needs exactly as it is, since your desire is to avoid passing your contexts around, and your custom scope will ensure that any @PolicyScoped bindings (presumably only your Contexts and their contents) have already been prepared ("seeded"). You'll also gain some nice multithreading capabilities, though you could get those just by turning your static reference into a static ThreadLocal.

You'll have to inject your policy-scoped object graph entirely between your enter and exit calls, or whatever you choose to name them. Take care to recognize that if you inject your PolicyContext in a constructor or field (saving it to the object's state), your object instance is now specific to that policy. This might seem obvious, but then again a teammate cavalierly injecting or caching dueDateCalculator may not realize that it was implicitly constructed to be the due date calculator only for policy #8675-309 and that it will provide bad answers for policy #5550-187. In particular any @Singleton object requiring a policy-scoped dependency should use a Provider, or else the singleton "remember" the policy even when you've exited your scope; this is an example of a "scope-widening injection" and Prasanna discusses it at length.

You may find it simpler to insist that your teammates never inject PolicyContext directly and instead always inject Provider<PolicyContext> (which you get for free if PolicyContext is injectable). That frees you to stop thinking about which policy was active when your objects were constructed, and instead trust the PolicyContext you receive when that object's method is run.

If an object has no dependencies, you don't need Guice to create it--that's just overkill. It's easy to move an object's creation to Guice once it sprouts so many dependencies that it's a pain to construct manually. Don't do it until you have to.

Finally, regarding Encapsulated Contexts, I happen to believe that the EC pattern is a valid refactoring as long as the context has no logic and the entire bundle of objects is applicable where the Context appears. If you can defend to me that every item in the Context is used 80% of the time you inject the context, then the code is probably shorter and easier to follow and you win. Remember that one of the benefits of dependency injection is that it is very easy to add or remove dependencies, so it becomes very easy to go from injecting one individually-bound Context property to injecting two individual Context properties to injecting the whole Context directly (and repeat for as many Contexts as you have).

That's just my view, though. Hope it helps!



标签: java scope guice