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]

Reply via email to