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