Inline:
On 7 Nov 2012, at 16:53, cowwoc wrote:
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?
Guice doesn't do it now, but Dagger does, and Guice ultimately will, if
you limit your use to patterns that are not inherently hard
(computationally) to analyze.
* We can't see dependencies from method signatures.
o True, but I argue that users shouldn't be aware of these
dependencies.
This is complex… it depends on the kind of object this is. Typically
objects you would want to create from factories based on data you have
in your local stack frame already (i.e., locals, parameters, etc.)
aren't ones that you want to hide the parameters of.
You're using the word dependency too broadly… what KIND of
dependencies are you talking about. In some concrete cases I might
agree with you. In others I would not.
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? :)
Can you please pick a non-absurd example of this? I would not use
dependency injection at all to supply Math.PI.
they're not pseudo-constants… they're scoped. Math.PI is global and
stateless data. That's quite different than a service object. It's
also different than a piece of context that can't be a constant, because
it's not universal (say, a "current user"), but is effectively like a
constant for that object's perspective. I don't mind injecting these,
in carefully controlled circumstances. But usually I find that the
benefits don't outweigh the costs on code clarity. This is subjective.
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.
I'm not sure how this is relevant to service vs. factory method. IF you
don't want the calling clients to see it, don't include it in the
service interface, and just inject it normally. You don't have to
change a factory method signature for non-assisted constructor parameter
changes.
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?
assisted-inject lets you mix this… actually, the end-clients should
only pay attention to the "assisted" parameters. The other injected
dependencies are entirely invisible to calling code, as it's injected by
guice.
* 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.
Possibly - I'd need to see this in practice to decide if it's worth the
payoff. My instinct says no, but I have things I'm used-to.
o Though, I argue that the entire point of Guice is to hide such
dependency lists from us.
Not all dependency lists, and not in all places at all times. Again -
you're using the one word dependency to refer to many kinds of things
that have different purposes, uses, structures, and life cycles. It's
important to define these kinds of things in your code to determine if
injecting them automatically, manually, or not at all is appropriate.
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.
Except it doesn't depend on these types in any way that it cares about.
It implicitly does, but the point of Guice-style DI is to separate
explicit from implicit dependencies. If you did what you describe, you
force all classes to depend on all classes, and altering behavior
elsewhere in the system would require changing it in all places, rather
than only behind the wall of encapsulations.
o Instead, Guice hides transitive dependencies from us for the
sake of clarity (as it should).
And other benefits to this hiding, but yes, agreed.
* 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.
If it's just an interface, I don't think it would clutter the file much.
If it's a class with method bodies, it would. I guess I just treat it
as two places I have to look. Sadly, there isn't (yet) a refactoring
plugin that can instruct IDEA/Eclipse to be aware of @Assisted
properties and their factory companions, so you can change it once. I
still maintain that this is a small cost for better cognitive result in
the resulting code.
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.
No - if you're re-organizing the dependencies, but no new things are
bound, then you don't have to change the bindings. The bindings aren't
the dependency graph, they're the manifest of what participates in the
system. If the types are all registered (@Provides or bind().to()) then
you're all set.
* 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?
Sure. These are a bit cerebral, but are quite familiar in most web apps
in most companies.
* Services: - global and expensive (often), providing facilities for
other classes
* A UserService is a global service. A DatabaseManager is a global
service At Google we have Gaia and Megastore equivalents, for example.
* Factories: - a kind of service that creates types that are frequently
created.
* A UserService that produces a User based on a lookup of data could
be considered a Factory
* A FooService, where Foos need to be created often, and may have
some injectable properties and some local state consumed to create it.
* Configuration/Context: A value type or complex object that is used to
provide context for services and other logic, but is (from its
dependents' perspective) largely immutable. A pseudo-constant, as you
call it.
* A User could be context/configuration if it's global from the
perspective of a given operation. If that operation is encapsulated in
an object, then a User could be its global context. But only if it's
playing a role such as a "current user". It's not configuration if it's
in a list of Users being acted upon. In that case it's a value type.
* A string that's annotated or the value of a key-value pair that has
some global meaning, such as the connection string to a database, etc.
* Value Type - Data. Primitive types, primitive holders/wrappers,
complex objects which represent the data of a program, collections of
such, etc.
* A User in some contexts is a value type.
* Primitive java types (ints, floats, strings) when created and
consumed in the normal flow of business logic.
* A Collection<Foo> operated upon by business logic.
Assisted Inject is highly useful when you have composite value types -
complex objects that need some data in their creation - data that makes
them unique (A User's userId) - and some services they rely on to do
their job, say, a LoggingService, or some other internals.
Whether something fits these categories depends on their usage. Even
something like a User can fit multiple roles in different contexts. So
it's not hard and fast. But things like assisted inject give you the
opportunity to start segregating these roles, and seeing how things are
used based on their patterns of use is a helpful signal in understanding
the code flow.
This is, as an aside, why I'm often uncomfortable with people writing
too much code that injects value types as context. It can be powerful,
but it can also confuse things, especially if that value type is context
here, but data over there. It can lead people to need to create
artificial scopes in Guice just to handle the fact that they structured
their code to treat a value type as context, always.
Hope that helps.
Chirstian.
--
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.