I watched an e-mail thread discuss how Singleton and Request scope in Guice were harmful, because Singletons can only depend on Singletons (without magic) and that Request Scoped items can only depend on Request Scoped and Singletons... followed by the idea that because "we're stateless" with minimal global state, so we shouldn't need global things. And because it's cheap to build things so we can build them all the time.
I feel that this perspective comes from a lot of fighting with scopes and scoping problems and probably is an honest attempt at resolving them - if you're stateless, then scope IS somewhat irrelevant... but it also turns out that if you're not stateless (by accident) and you go scope-less, then you have state accidentally scattered all around your object graph without realizing you've copied it. Leave aside entirely that you're going to thrash garbage collection because your dependencies are stored in ivars which will go on the heap...
So what should happen here?
Other than Guice, all other dependency-injection containers of any substantial distribution use "Singleton" as the default scope, not "scopeless" (or what spring calls prototype scope). This is because Singleton is actually cleaner and easier to understand. There is one copy of this in the system... so it needs to either be stateless or carefully guard its state for multi-threaded access. But also the lifecycle is clearer - app starts up, component starts up, component shuts down, app shuts down. Scopeless (Guice "default" scope) actually have an arbitrary lifecycle based on who asks for them.
If you have Foo -> Bar (where -> means depends-on), and Foo is any real scope (singleton, session, request, per-thread, whatever), but Bar is scopeless (meaning a new one is created on every demand), then Bar's lifecycle is different if Foo depends on it than if Bash depends on it because it attaches to the lifecycle (lifetime... or scope, if you will <ahem>) of the dependent component.
This is freaky because it means it's sort of indeterminate until used (I call it the Heisenberg scope). And each time it's used it could be different.
Again, if it's stateless, no problem... but if it's stateless, Singleton is no problem... and cleaner... because you'll uncover scoping problems more quickly with more restrictive scope policies. But moving everything to no-scope means no clear lifecycle... or rather, whatever lifecycle and lifetime you happen to have in whatever calls it.
I think people look at scope as "magical" - especially in the Guice community. I don't see this kind of thrash in Picocontainer, Tapestry-IOC, or Spring user communities. And I think it's because "prototype" scope (scopeless) is seen as a quasi-factory behaviour, not a dependency/collaborator injection behaviour. The two are subtly different, and I think the distinction is lost in the Guice user community. I have ideas as to why this distinction arose between the Guice community's thinking and others', but I'll leave that for another post.
The point is, Scope implies a validity within a lifetime, and if something is stateless, there's no reason it shouldn't be the one and only copy with a lifetime of the entire application's lifetime. I've long posited that "games with scopes" is a dangerous thing in dependency injection, but this is solving the problem by dropping a 16 ton weight on your own head. It uses the most magical quasi-scope to create a fan of instances where a very small set of collaborators are required.
I'm getting close to believing that Fowler, Martin, and others were wrong, and that Dependency Injection (heck, occasionally O-O) are just too dangerous. Seriously. I can't imagine not using them, personally, but I find so many teams and projects where they just think about the problem so unhelpfully and then their clean-up efforts are worse than the mess they created. <sigh>
No comments:
Post a Comment