Hi, I submitted my work to http://codereview.appspot.com/2896041/
Feel free to comment. I (or, You) still have to decide what is the best approach: dynamic services or configuration groups. Tom. On Tue, 02 Nov 2010 00:49:59 +0100, Tom van Dijk <[email protected]> wrote: > Hi, > > Because the "dynamic service" approach might be conceptually tricky, I > came up with a different approach, called configuration groups. This > doesn't mean that I've thrown "dynamic services" away, just that I spend > some time on a different solution. It's a free market, I suppose the > best solution wins. I'm also not sure if there IS an actual "market" for > dynamic services. > > Summary: you can add @ConfigurationGroup("...") to N services, then if > you contribute M markers, NxM services will be created. Service > dependencies are automatically resolved to use services with the same > marker. > > In order to prevent any other core overloads in your (and Howard's) > brain, I'll try to explain how it works step by step. > > === CONFIGURATION GROUPS === > > A configuration group means that of the same service implementation, > multiple services will be created, for example with id's "Service:Red", > "Service:Blue" and markers @Red and @Blue. The id's are automatically > generated in the format <service>:<marker>, but implementations do not > need to be aware of this. In fact, if there are name clashes, they are > automatically resolved by adding a number. By default, if no markers are > contributed, only "Service" will be created without markers. If "null" > is contributed to the configuration group, this service will be created > as well. > > I defined an annotation that can be added to a service: > @ConfigurationGroup("yourGroupIdHere") > public Service buildService(...) { ... } > > This works with the service binder too: > binder.bind(interface, > implementation).withConfigurationGroup("hibernate-core"); > > You can contribute to configuration groups using a second annotation I > defined: > @ContributeConfigurationGroup("yourGroupIdHere") > public static void contribute(Configuration<Class> configuration) { > configuration.add(Red.class); > configuration.add(Blue.class); > configuration.add(null); > } > > This method MUST be "public static void (Configuration<Class>)". This is > important. We want to know the configurations to create before creating > the Module objects, it makes life much simpler. If you add a "null" > marker, there will also be a configuration without added markers. > > A contribution to the original service will be contributed to all its > copies as well. You can contribute to specific copies using their marker: > Contribute(Service.class) @Red > public void someMethodName(...) > > === INJECTION === > > We can use a third annotation to get information about our marker(s): > public static Service buildService(@ConfigurationGroupMarker Class > myMarker, @ConfigurationGroupMarker Set<Class> allMarkers) { ... } > > Indeed, the same annotation (@ConfigurationGroupMarker) is used for > Class and for Sets of Class. You can ONLY use this annotation on > services that are in a configuration group. > > The set of markers: > - empty or null, meaning that the services are defined without markers > - non-empty, including null, meaning that the services are defined > without markers and with all markers > - non-empty, without null, meaning that the services are only defined > with markers > > Now there are two extra injection rules, to make sure that all services > in the same configuration group only use services that have their marker > as well: > * @InjectService(serviceId) and serviceId is in our configuration group > : get service with same marker > * Injection of a service interface also in our configuration group: > first try to resolve it with my marker added, then just default behavior > > So you can have the parameter "@Local Service something", and it will > try to resolve the service with markers @Local and @MyMarker. > > There are also extra injection rules for all services (inside or outside > configuration groups) and components (in core): > * @InjectService with a marker: get the service created from this > service with the marker > *If an injection results in multiple services with the same interface, > but all in the same configuration group, IoC will return the unmarked > (null) service if it exists, or throw an exception. > * I modified @Inject for components (in core), so it will first resolve > services/marker matches and then use the MasterObjectProvider (so they > will be handled BEFORE ServiceOverride, just like getObject(...) of the > registry already does for the IoC services) ==> was this a bug in > tapestry-core? > > > === USAGE FOR HIBERNATE === > > I've used this for hibernate-core. > * Add @ConfigurationGroup("hibernate-core") annotation to all relevant > services > * Add a HibernateUtil class, that uses ServiceActivityScoreboard to find > out which sessions are realized (used by hibernate integration with > tapestry) > * Modify DefaultHibernateConfigurer to use /hibernate-<marker>.cfg.xml > * Modify CommitAfter to accept arrays of markers > > I had to do a little more in hibernate integration with tapestry-core, > because it needs to figure out to which session an object belongs (this > is where HibernateUtil from core is used, making sure no unnecessary > connections are made to the database) > > Good news! All test cases work fine if no markers are contributed, so I > expect existing applications will work just fine. My test web > application also works, so the case of multiple databases works. Also, I > can sneak in the ServiceOverride without problems now. > > > === TECHNICAL STUFF === > > Now, in order to make it all go smooth, I had to modify the injection > stuff in InternalUtils and other objects. Most importantly, I had to add > a lot of functionality to ObjectLocator: > * getService(serviceId, serviceInterface, Class configurationMarker) > * getObject(objectType, annotationProvider, boolean required?) > * getObject(serviceInterface, Class... markers) > * getMarkers() = returns all used markers in the Registry, used for > filtering of annotations in InternalUtils > * Set<Class> getConfigurationGroupMarkers(String groupId) = returns > all markers used in a configuration group (including null) > > ServiceResources also implements ConfigurationGroupResources with a > number of methods, and I added extra methods to InternalRegistry, so > RegistryImpl is a bit bigger. These are mostly changes that are > necessary to implement the injection changes. > > I needed to modify the DefaultInjectionProvider in tapestry-core to use > "locator.getObject(...) with required=false", because @Inject with > markers would not work otherwise. This may be a bug in Tapestry-core. > I'm not sure, but the DefaultInjectionProvider right now uses ONLY the > MasterObjectProvider, while its equivalent in IoC first checks > services+marker matches. > > I'm sitting on a very large number of modified files (77 files right > now, 23 new files mostly for test cases and stuff), partially because > various services needed tiny changes to work with the new interfaces > (e.g. modified tests and mocks) > > > > === CONCLUSIONS === > > While both approaches work, this approach has the advantage of offering > very concise syntax for the general problem of "services with multiple > configurations", of which "hibernate with multiple databases" is a > specific case. > > Dynamic services offers more flexibility, being a more general solution > that solves the problem of "services that cannot be defined at > compile-time" but in return, it takes a bit more coding to convert a > bunch of ordinary services to a bunch of dynamic services, as opposed to > just adding an annotation here and there. > > I do not have the code on git or anywhere else yet. I ought to add > javadoc first... and I expect it will take at least a few weeks until > 5.3 anyway, so there's no hurry... ( well I want to use this in a > project for a customer, but other than that... ) Also, I still need to > add proper test cases to tapestry-hibernate for the multiple databases > case, instead of the custom web application I use now. > > Anyone know how I can run a test webapp without running all that > selenium stuff? > > Feel free to give me feedback. I'm sort of poking around blindly if you > experts don't say anything. > > > Tom. > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [email protected] > For additional commands, e-mail: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
