Hi Christian,
Let's work through these one by one.
* We lose the ability to static analysis the code to determine if the
wiring is correct.
o I've never heard of using static analysis to checking wiring.
Can you please point me to an example?
* We can't see dependencies from method signatures.
o True, but I argue that users shouldn't be aware of these
dependencies.
o We're asking users to pass in what amount to pseudo-constants.
Imagine if we used inversion of control to pass in Math.PI.
Would users be happy to know that a class depends on the value
of PI? Who cares? :)
o Typically, injected types are bound once and users never
see/hear about it ever again. Often, inject values come from
external frameworks (e.g. Jersey). This stuff "just works" 99%
of the time.
o I argue users should only see parameters that are most likely to
concern them. When I construct a type where some parameters are
injected and some are supplied by the user, I only never pay
attention to the injected types. Should I?
* This approach makes it harder to maintain dependency lists.
o I suppose you could generate dependency lists from your
Module(s) or invoke the Guice SPIs.
o Though, I argue that the entire point of Guice is to hide such
dependency lists from us.
o If we really cared to see them in their glory, we'd pass all
transitive dependencies into the top-level class. That way it
would be clear that we really depend on all of these types.
o Instead, Guice hides transitive dependencies from us for the
sake of clarity (as it should).
* Place AssistedInject factories near the constructor.
o I agree this helps but like you said it ends up cluttering the
file so I also ended up moving it to the bottom.
o And remember, the module/bindings still reside in a separate
file, so you're still going to have a harder time keeping the
two synchronized.
* You brought up an interesting point about being able to inject
different kids of injected things (e.g. collaborating services, etc)
but I didn't really understand what you meant. Can you please
provide a more concrete example?
Thanks,
Gili
On 07/11/2012 4:06 PM, Christian Gruber wrote:
Well, for starters, you've now created a dependency on the injector,
which is valid but I find makes code substantially more confusing in
the long-run. It also means that we could never EVER analyze your code
statically to determine if your wiring was correct (that is, all
dependencies are bound) because you've internalized the logic of the
dependency fulfillment into a code-path instead of a declaration.
Also, you can't see the dependencies from without - say, from method
signatures or otherwise, so it's harder to reason things through. You
can always look at the code, but the constructor signature is now
meaningless - it doesn't semantically include the dependencies - it
includes a piece of infrastructure.
You're right - there's a bit more work to do from the outside with
assisted injection in terms of maintaining dependency lists. But the
clarity in the code I find to be valuable.
In practice, I don't find it that difficult to keep these things clear.
public class Foo {
//private variables
// factory interface near constructor.
public interface Factory {
Foo create(Consumable someValue);
}
Foo(Dependency dep, OtherDependency dep2, @Assisted Consumable
someValue) {
...
}
}
This way, if I need to change that list, I change the two nearly
beside each other - easy to see.
The other problem is that with assisted injection, you now can
separate different KINDS of injected things - collaborating services,
configuration values, consumables, and pass them around differently.
Consumables (value types, local differences) have quite different life
cycles from services, etc. So if you do what you're suggesting, you
absolutely start to need to play scope games. Because if you have
values that aren't just created once for each request being part of
the object's lifecycle, you now have to create narrower and narrower
@Scopes in order to support these shorter lifetimes.
Sometimes things really just need to be on the call-stack. And having
a factory break as part of a factory call is a far clearer error (and
stack-trace) than a deep failure of injection. They might be
systemically equivalent, but their meaning to the coder is different,
and finding the errors later are quite different.
Generally, anything you might want to use Guice as a factory for, I
tend to recommend using assisted injection, because it creates
separate, traceable factories for each type. Yes, it's "more work" in
one sense - but I think it's a better organization of code.
Injecting the injector is something that is possible, and sometimes
necessary to support legacy code. But I think the problems outweigh
the ease you think it'll buy you - especially for down-stream
maintainers.
Christian
On 7 Nov 2012, at 15:50, Gili wrote:
Hi,
I've been using Guice over the past couple of years and it worked quite
well for me. However, every once in a while I run across a recurring
problem and I'd like to get your feedback on it. The problem arises
when I
run across a constructor that requires a mix of Guice-injected and
user-supplied values. For example:
public MyService(UriInfo uriInfo, String userName)
This is precisely the use-case AssistedInject is meant to address but
the
more I run into this code, the more I am leaning towards using the
Service
Locator pattern [1] instead. AssistedInject requires me to construct and
bind one factory per class. This means that every time I add/remove a
class, I need to add/remove a binding. It means every time I add/remove
user-supplied parameters, I need to also add/remove parameters from the
factory. Initially it didn't seem like a big deal but time I've
questioned
the cost/benefit of this approach. The resulting code is harder to
read and
maintain, and for what? Nowadays I replace:
@Inject
public MyService(First first, Second second, Third third, @Assisted
String userName)
with
@Inject
public MyService(Injector injector, String userName)
{
this.first = injector.getInstance(First.class);
this.second = injector.getInstance(Second.class);
this.third = injector.getInstance(Third.class);
}
and simply inject classes on-demand using injector. Essentially I'm
using
Guice as a Service Locator. The way I see it:
- The classes/constructors are a lot easier to maintain.
- No need to enumerate the injected parameters in the Javadoc. I've
always found it annoying to have the Javadoc cluttered with
parameters the
user doesn't really care about (besides the fact it was a lot of work to
keep such documentation up-to-date).
- This still implements the inversion of control pattern so the code is
easily testable.
- The dependencies might not be listed as constructor parameters, but
they still visible near the top of the constructor.
- To clarify, I only advocate this approach when mixing injected and
user-supplied parameters. I still use normal constructor injection
when all
parameters come from Guice.
So really, what's the harm? I'd love to get your feedback on this.
Thanks,
Gili
[1] http://martinfowler.com/articles/injection.html#UsingAServiceLocator
--
You received this message because you are subscribed to the Google
Groups "google-guice" group.
To view this discussion on the web visit
https://groups.google.com/d/msg/google-guice/-/HSTXZ7On-acJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice?hl=en.
--
You received this message because you are subscribed to the Google Groups
"google-guice" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice?hl=en.