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.

Reply via email to