leon breedt wrote:

> Pseudo-code:
> 
> reg.register(:config) { Configuration.new }
> reg.intercept(:config).with { |reg| ConfigurationInterceptor.new(reg} }
> reg.register(:logger) { |reg| Logger.new(reg.config.filename) }
> 
> ....
> 
> somewhere else in application: reg.config.filename = "newfile"
> 
> 
> Because the construction block for :logger called reg.config, the
> registry is aware that it depends on :config. The reg.config.filename
> assign would have been caught by the interceptor as a "write", and
> once completed, the interceptor would send a dirty(:config) message to
> the registry.

Hmmm. But there's still the issue of the reg.config.filename invocation 
(to use the example above) knowing the context in which it is being 
called. It would need to know, specifically, which service point 
(:logger) was being instantiated, so that it could relate the dependency 
back to that service... hmmm.

> 
> I was thinking, for this particular approach to work, |reg| in the
> :logger construction block might need to be a proxy that collects the
> services used in the block, and adds them to a dependency graph,
> instead of being the registry itself (for thread-safety?)

But what about something like this happening:

   reg.register(:logger) { |r| Logger.new( reg.config.filename ) }
   ^^^                     ^^^             ^^^

In other words, there is nothing that prevents the block from referring 
to registries and containers outside the block. This means that it 
really can't be a proxy object--it has to be the container itself that 
knows the dependency information.

Perhaps you could use a thread-local variable to keep track of the 
service that is being constructed. It would need to be a stack, though, 
since instantiating one service can cascade into multiple service 
instantiations.

If the #register method stored a stack of service names that are 
currently being constructed by each thread in a thread-local variable, 
your interceptor could use that variable to relate the dependency back 
to the registry...

Hmm. But this falls apart when the container itself is a dependency, 
since the service that depends thus on the container could query the 
container at any time, not just during the service's instantiation. This 
means that the service could effectively have new dependencies at 
arbitrary points during its lifecycle:

   reg.register(:svc) { SomeNewService.new(reg) }
   reg.svc.reg.another_service
   ...

I guess what I'm feeling is that there is no general way to know, 
definitively, all of the dependencies of a service, especially in a 
dynamic language like Ruby. Without putting restrictions on when and how 
services are defined (like Copland does), I'm not sure how to come up 
with a general solution to this.

That said, what about a slightly less transparent approach:

   reg.register(:config) { RefreshableProxy.new { Config.new } }
   reg.register(:logger) { RefreshableProxy.new( reg, :config ) {
      Logger.new( reg.config.filename } }

   logger = reg.logger
   reg.config.refresh!
   ...

What the above snippet is trying to demonstrate is a kind of observer 
pattern. In the first registration, we create a (imaginary) 
RefreshableProxy instance that wraps the given configuration instance. 
In the second, we create another RefreshableProxy instance that wraps 
the Logger.new instantiation. The second one also declares itself to be 
an observer of the :config service in the given container.

When #refresh! is invoked on a RefreshableProxy, it notifies all of its 
observers that it changed, and the next time a method is invoked on that 
proxy it re-executes its associated block. It's really a specialized 
kind of deferred instantiation.

Does that make sense? It could be added to the framework so that EVERY 
service is automatically wrapped in a RefreshableProxy, but that would 
add a lot of overhead that would rarely be needed. Doing it manually 
requires you to explicitly specify the dependency graph in the form of 
observers, but also incurs a lot less overhead (in general).

Now, if that is what you were wanting, the above COULD be implemented as 
a new service model:

   class RefreshableServiceModel
     ...
   end

   reg.service_models[:refreshable] = RefreshableServiceModel
   reg.register( :config, :model=>:refreshable ) { Config.new }
   reg.register( :logger, :model=>:refreshable, :observe=>[:config] ) {
     Logger.new( reg.config.filename ) }

   config = reg.config
   logger = reg.logger
   config.refresh!
   ...

If you would like a concrete implementation of RefreshableServiceModel, 
let me know. Otherwise, I'll leave it as an exercise for the reader. ;)

- Jamis

-- 
Jamis Buck
jgb3 / email.byu.edu
http://www.jamisbuck.org/jamis